在MVC中从同步调用异步而没有等待时,TPL任务死锁 [英] TPL Task deadlock when calling async from sync without await in MVC

查看:110
本文介绍了在MVC中从同步调用异步而没有等待时,TPL任务死锁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我了解在使用.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.

发生这种情况:

  1. HomeController.Index()操作已完成
  2. Log.Info("Begin")已执行.
  3. SaveToDb()完成了这项工作,但在完成后是否挂起则未知.
  4. PublishTomessageQueue()不会执行此工作,如果它从未启动过或只是停留在其中,则不知道.
  5. 均未调用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.

第二个主要问题是这是忘了忘了",这在ASP.NET上是一个非常糟糕的主意.这是一个坏主意,因为ASP.NET完全围绕请求/响应系统设计.它具有非常有限的功能来处理请求中不存在的代码.当没有活动请求时,ASP.NET可以(并且将)定期回收您的应用程序域和工作进程(需要 来保持工作状态整洁).完全不知道您的Trigger代码正在运行,因为调用它的请求已经完成-因此,您正在运行的代码可能会定期消失.

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 awaits 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆