在MVC

时间:2017-08-03 07:40:24

标签: c# asp.net-mvc asynchronous task deadlock

我理解在同步MVC方法中调用异步方法时有一个TPL死锁陷阱,同时使用.Wait()或.Result等待任务完成。

但是我们在MVC应用程序中发现了一个奇怪的行为:同步动作调用异步方法,但由于它是一个触发器,我们从不等待它完成。但是,异步方法似乎陷入了困境。

代码如下,这个奇怪的问题不是100%发生的。它只是发生在某个时候。

当它发生时:

  1. HomeController.Index()操作已完成
  2. Log.Info(“Begin”)已执行。
  3. SaveToDb()完成了这项工作,但是如果它在完成后挂起则不知道。
  4. PublishTomessageQueue()不会完成这项工作,如果它从未开始或只是卡在里面,则不知道。
  5. 未调用Log.Info(“Finish”)/ Log.Error(“Error”)。
  6. 大部分时间,代码都按预期工作。

    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();
        }
    
    
    }
    

2 个答案:

答案 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;代码转换为实际请求。例如,Indexawait 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线程)。