我知道在同步 MVC 方法中调用异步方法,同时使用 .Wait() 或 .Result 等待任务完成时,存在 TPL 死锁陷阱。
但我们刚刚在 MVC 应用程序中发现了一个奇怪的行为:同步操作调用异步方法,但由于它是触发器,因此我们从未等待它完成。尽管如此,异步方法似乎还是卡住了。
代码如下,这个奇怪的问题并不是100%发生的。它只是有时会发生。
当它发生时:
- HomeController.Index() 操作已完成
- Log.Info("Begin") 已执行。
- SaveToDb() 完成了这项工作,但未知完成后是否挂起。
- PublishTomessageQueue() 无法完成这项工作,不知道它是否从未启动或只是卡在里面。
- Log.Info("Finish")/Log.Error("Error") 都没有被调用。
大多数时候,代码会按预期工作。
ISomeInterface.Trigger() 也被从其他地方调用,Windows 服务而不是 mvc,但这种奇怪的行为永远不会发生。
所以我的问题是,异步任务是否有可能陷入死锁没有 .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();
}
}
异步方法似乎卡住了......有时会发生......大多数时候,代码按预期工作。
是的。这段代码有几个主要问题。
首先,它可以尝试恢复不再存在的请求上下文。例如,请求Index
进来,ASP.NET 为该线程创建一个新的请求上下文。然后它调用Index
在该请求上下文中,并且Index
calls Some.Trigger
, 什么时候Trigger
击中第一个await
, it 默认情况下捕获该上下文 https://blog.stephencleary.com/2012/02/async-and-await.html并返回一个未完成的任务Index
. Index
然后返回,通知 ASP.NET 请求已完成; ASP.NET 发送响应,然后删除该请求上下文。稍后的,Trigger
准备好在其后恢复await
,并尝试恢复该请求上下文...但它不再存在(请求已完成)。混乱随之而来。
第二个主要问题是,这是“即发即忘”,这是一个真是个坏主意在 ASP.NET 上 https://blog.stephencleary.com/2014/06/fire-and-forget-on-asp-net.html。这是一个坏主意,因为 ASP.NET 完全是围绕请求/响应系统设计的;它对于处理请求中不存在的代码的功能非常有限。当没有活动请求时,ASP.NET 可以(并且将会)定期回收您的应用程序域和工作进程(这是required以保持物品清洁)。它完全不知道你的Trigger
代码正在运行,因为调用它的请求已经完成 - 因此,您正在运行的代码可能会定期消失。
最简单的解决方案是将此“触发”代码移至实际请求中。例如。,Index
can await
返回的任务Trigger
。或者让您的页面代码向 API 发出 AJAX 调用,该 API 调用Trigger
(and await
s it).
如果这是不可行的,那么我会推荐一个合适的分布式系统:Index
将“触发请求”放入可靠队列中并由独立后端(例如 Win32 服务)处理。或者您可以使用现成的解决方案,例如Hangfire https://www.hangfire.io/.
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)