【问题标题】:Error: An asynchronous module or handler completed while an asynchronous operation was still pending错误:异步模块或处理程序已完成,而异步操作仍处于挂起状态
【发布时间】:2015-08-29 15:58:05
【问题描述】:

我有一个控制器操作方法来保存用户详细信息,如下所示。

public async Task<ActionResult> SaveUser(ViewModel.VM_CreateUser user)
{
    var result = await _user.Save(userDetails);
    return Json(new { Success = String.IsNullOrEmpty(result) });
}

到目前为止,上述4行功能没有问题。

public async Task<ActionResult> SaveUser(ViewModel.VM_CreateUser user)
{
    var result = await _user.Save(userDetails);
    new MailController().CreateUser(user.userDetails); //<==This is problem line of code.
}

下面是我的邮件控制器。

public class MailController : MailerBase
{
    public void CreateUser(ViewModel.VM_User user)
    {
        To.Add(user.EmailAddress);
        From = System.Configuration.ConfigurationManager.AppSettings["emailSender"];
        Subject = "Hi";
        Email("CreateUser", user).DeliverAsync();
    }
}

在上面的代码中,我在执行电子邮件发送代码时遇到了问题。我收到以下错误消息。

异步模块或处理程序在异步完成时完成 操作仍在等待中

请提出纠正措施!!

【问题讨论】:

    标签: c# asp.net-mvc asp.net-mvc-3 asp.net-mvc-4


    【解决方案1】:

    这是因为async 方法跟踪它们的完成,甚至async void。他们通过在启动时注册 SynchronizationContext 并在返回时标记操作完成来做到这一点。 ASP.NET 跟踪所有创建的操作,并要求它们在您的操作返回之前全部完成,否则它会向 HTTP 客户端返回一个错误。如果您需要运行“即发即弃”方法,则必须手动避免在 ASP.NET SynchronizationContext 上启动它。例如,await Task.Run(() =&gt; CallFireAndForget()) (await,这样您就可以等待同步部分在线程池上运行。这是因为CallFireAndForget()async void。触发一个返回Task 作为触发的方法-忘记了,您必须明确避免将Task 像这样返回Task.Run()await Task.Run(() =&gt; { CallFireAndForgetAsync(); }))。

    另一种显示相同错误消息的方法应该是将其写入异步操作:

    // BAD CODE, DEMONSTRATION ONLY!
    AsyncOperationManager.CreateOperation();
    

    如果您使用AsyncOperation API,您必须确保在允许操作方法返回之前调用AsyncOperation.OperationCompleted()

    在你的情况下,如果邮件发送真的是一个即发即弃的任务,你可以在 CreateUser 将其签名更改为 async Task CreateUserAsync(ViewModel.VM_User user) 后在其内部执行以下操作:

    await Task.Run(() => Email("CreateUser", user).DeliverAsync());
    

    在你的行动中:

    await new MailController().CreateUserAsync(user.userDetails);
    

    理想情况下,您不会将Task.Run() 用于此类事情。相反,您可以暂时替换当前的SynchronizationContext注意:永远不要将await 放在try{} 中—相反,如果需要,将Task 存储在本地并在恢复后等待它原始SynchronizationContext(使用诸如SynchronizationContextSwitcher.NoContext() (source) 之类的原语可能更安全)):

    var originalSynchronizationContext = SynchronizationContext.Current;
    try
    {
        SynchronizationContext.SetSynchronizationContext(null);
        new MailController().CreateUser(user.userDetails);
    }
    finally
    {
        SynchronizationContext.SetSynchronizationContext(originalSynchronizationContext);
    }
    

    【讨论】:

      【解决方案2】:

      DeliverAsync 是否返回任务?如果有,试试这个。

      public class MailController : MailerBase
      {
          public async Task CreateUserAsync(ViewModel.VM_User user)
          {
              To.Add(user.EmailAddress);
              From = System.Configuration.ConfigurationManager.AppSettings["emailSender"];
              Subject = "Hi";
              await (Email("CreateUser", user).DeliverAsync());
          }
      }
      

      然后在你的控制器中,等待 CreateUserAsync 返回的任务。

          public async Task<ActionResult> SaveUser(ViewModel.VM_CreateUser user)
          {
              var result = await _user.Save(userDetails);
              await (new MailController().CreateUserAsync(user.userDetails)); 
              return Json(new { Success = String.IsNullOrEmpty(result) });
          }
      

      注意:如果您的目标是让发送电子邮件成为后台触发并忘记操作,那么不是这样。

      【讨论】:

      • 我收到一个编译错误。 Cannot await void.
      • 如果 DeliverAsync() 返回 void,那么这将不起作用。否则,您是否将 CreateUser 更改为返回任务
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-30
      • 2013-03-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多