我理解在同步MVC方法中调用异步方法时有一个TPL死锁陷阱,同时使用.Wait()或.Result等待任务完成。
但是我们在MVC应用程序中发现了一个奇怪的行为:同步动作调用异步方法,但由于它是一个触发器,我们从不等待它完成。但是,异步方法似乎陷入了困境。
代码如下,这个奇怪的问题不是100%发生的。它只是发生在某个时候。
当它发生时:
大部分时间,代码都按预期工作。
ISomeInterface.Trigger()也是从其他地方调用的,而不是mvc,但这种奇怪的行为从未发生过。
所以我的问题是,即使 WITHOUT .Wait()也没有.Result ,异步任务是否可能陷入死锁?
非常感谢。
public interface ISomeInterface
{
Task Trigger();
}
public class SomeClass
{
public async Task Trigger()
{
Log.Info("Begin");
try
{
await SaveToDb();
await PublishToMessageQueue();
Log.Info("Finish");
}
catch (Exception ex)
{
Log.Error("Error");
}
}
}
public class HomeController : Controller
{
public ISomeInterface Some { get; set; }
public ActionResult Index()
{
Some.Trigger(); //<----- The thread is not blocked here.
return View();
}
}
答案 0 :(得分:2)
异步方法似乎陷入困境......它只是在某个时候发生......大部分时间,代码按预期工作。
是。这段代码存在一些主要问题。
首先,它可以尝试在不再存在的请求上下文中恢复。例如,Index
的请求进来,ASP.NET为该线程创建了一个新的请求上下文。然后,它会在该请求上下文中调用Index
,并Index
调用Some.Trigger
,当Trigger
点击其第一个await
时,它captures that context by default并返回Index
的任务不完整。然后Index
返回,通知ASP.NET请求已完成; ASP.NET发送响应,然后删除该请求上下文。稍后,Trigger
准备好在其await
之后恢复,并尝试在该请求上下文中继续...但它不再存在(请求已经完成)。随之而来的是混乱。
第二个主要问题是"fire and forget", which is a really bad idea on ASP.NET。这是一个坏主意,因为ASP.NET完全围绕请求/响应系统设计;它具有非常有限的工具来处理作为请求的一部分不存在的代码。当没有活动请求时,ASP.NET可以(并且将会)定期回收您的应用程序域和工作进程(这是必需以保持清洁)。它完全不知道您的Trigger
代码正在运行,因为调用它的请求已经完成 - 因此,您运行的代码可能会定期消失。
最简单的解决方案是移动这个&#34;触发&#34;代码转换为实际请求。例如,Index
可await
Trigger
返回的任务。或者让您的网页代码向调用Trigger
(以及await
s)的API发出AJAX调用。
如果这不可行,那么我建议使用合适的分布式系统:让Index
发出&#34;触发请求&#34;进入可靠的队列并由独立的后端(例如,Win32服务)处理。或者您可以使用现成的解决方案,例如Hangfire。
答案 1 :(得分:1)
即使没有.Wait()也没有.Result,异步任务是否可能陷入死锁?
是的,有可能。默认情况下,执行将在await之后编组回原始线程。但是如果线程由于某种原因不可用或被阻塞,则可能发生死锁。
在您的情况下,您不确定这是否也是问题,但您可以尝试以下方法:
await SaveToDb().ConfigureAwait(false);
await PublishToMessageQueue().ConfigureAwait(false);
ConfigureAwait(false)
告诉跑步者状态机可以在任何线程上继续执行。在大多数情况下,这没关系。只有在特殊情况下才需要编组回原始线程(例如WinForms或WPF UI线程)。