在MVC中从同步调用异步而没有等待时,TPL任务死锁 [英] TPL Task deadlock when calling async from sync without await in MVC
问题描述
我了解在使用.Wait()或.Result等待任务完成之前,在同步MVC方法中调用异步方法时会出现TPL死锁陷阱.
I understand there's a TPL deadlock trap when calling async method within a sync MVC method, while using .Wait() or .Result to wait till the task complete.
但是我们只是在MVC应用程序中发现了一个奇怪的行为:sync操作调用了async方法,但是由于它是触发器,所以我们从未等待它完成.仍然,异步方法似乎卡住了.
But we just found a strange behaviour in our MVC application: The sync action calls an async method, but since it's a trigger, we never waited it complete. Still, the async method seems stucked.
代码如下所示,这种奇怪的问题不是100%发生的.它只是在某个时间发生.
Code is like below, this strange issue not 100% happens. It just happens sometime.
发生这种情况:
- HomeController.Index()操作已完成
- Log.Info("Begin")已执行.
- SaveToDb()完成了这项工作,但在完成后是否挂起则未知.
- PublishTomessageQueue()不会执行此工作,如果它从未启动过或只是停留在其中,则不知道.
- 均未调用Log.Info("Finish")/Log.Error("Error").
大多数时候,代码都能按预期工作.
Most of time, code works as expected.
ISomeInterface.Trigger()也从其他地方(Windows服务而不是mvc)中调用,但是这种奇怪的行为永远不会发生.
The ISomeInterface.Trigger() also been called from other places, windows services rather than the mvc, but this odd behaviour never happens.
所以我的问题是,即使没有.Wait()也没有.Result ,异步任务是否有可能陷入僵局?
So my question is, would it be possible that async tasks get into deadlock even WITHOUT .Wait() nor .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();
}
}
推荐答案
异步方法似乎卡住了...它只是在某个时间发生...大多数情况下,代码按预期工作.
the async method seems stucked... It just happens sometime... Most of time, code works as expected.
是的.这段代码有两个主要问题.
Yes. There's a couple of major problems with this code.
首先,它可以尝试在不再存在的请求上下文中恢复.例如对于Index
的请求进来,并且ASP.NET为该线程创建一个新的请求上下文.然后,它在该请求上下文中调用Index
,并且Index
调用Some.Trigger
,并且当Trigger
达到其第一个await
时,它会
First, it can attempt to resume on a request context that no longer exists. For example, the request for Index
comes in, and ASP.NET creates a new request context for that thread. It then invokes Index
within that request context, and Index
calls Some.Trigger
, and when Trigger
hits its first await
, it captures that context by default and returns an incomplete task to Index
. Index
then returns, notifying ASP.NET that the request is complete; ASP.NET sends the response and then tears down that request context. Later on, Trigger
is ready to resume after its await
, and attempts to resume on that request context... but it no longer exists (the request has already completed). Pandemonium ensues.
The second major problem is that this is "fire and forget", which is a really bad idea on ASP.NET. It's a bad idea because ASP.NET is designed entirely around a request/response system; it has very limited facilities for working with code that does not exist as part of a request. When there are no active requests, ASP.NET can (and will) periodically recycle your app domain and worker process (this is required to keep things clean). It has absolutely no idea that your Trigger
code is running because the request that called it has already completed - thus, your running code can just disappear periodically.
最简单的解决方案是将触发"代码移到实际请求中.例如,Index
可以await
Trigger
返回的任务.或者让您的页面代码对调用Trigger
(并await
s)的API发出AJAX调用.
The easiest solution is to move this "trigger" code into an actual request. E.g., Index
can await
the task returned by Trigger
. Or have your page code issue an AJAX call to an API that calls Trigger
(and await
s it).
如果这不可行,那么我建议使用适当的分布式系统:让Index
将触发请求"放入可靠的队列中,并由独立的后端(例如Win32服务)进行处理.或者,您可以使用现成的解决方案,例如 Hangfire .
If this isn't doable, then I'd recommend a proper distributed system: have Index
place a "trigger request" into a reliable queue and have it processed by an independent backend (e.g., Win32 service). Or you could use an off-the-shelf solution like Hangfire.
这篇关于在MVC中从同步调用异步而没有等待时,TPL任务死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!