错误:异步模块或处理程序在异步操作仍处于挂起状态时完成

Pan*_*kaj 0 c# asp.net-mvc asp.net-mvc-3 asp.net-mvc-4

我有一个Controller Action方法来保存用户详细信息,如下所示。

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

到目前为止,以上四行功能都没有问题。

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.
}
Run Code Online (Sandbox Code Playgroud)

下面是我的邮件控制器。

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

异步模块或处理程序在异步操作仍挂起的同时完成

请提出纠正措施!

bin*_*nki 5

这是因为async方法跟踪它们的完成,甚至async void。他们通过SynchronizationContext在启动时注册并在返回时将操作标记为完成来完成此操作。ASP.NET会跟踪所有已创建的操作,并要求它们在操作返回之前完成所有操作,否则它将向HTTP客户端返回错误。如果需要运行“即发即弃”方法,则必须手动避免在ASP.NET上启动它SynchronizationContext。例如,await Task.Run(() => CallFireAndForget())await让你等待同步部分线程池运行这工作,因为,CallFireAndForget()async void要发射它返回的方法。Task作为发射后不管,你必须明确地避免返回TaskTask.Run()这样的:await Task.Run(() => { CallFireAndForgetAsync(); })) 。

出现相同错误消息的另一种方法应该是在异步操作中编写此消息:

// BAD CODE, DEMONSTRATION ONLY!
AsyncOperationManager.CreateOperation();
Run Code Online (Sandbox Code Playgroud)

如果使用AsyncOperationAPI,则必须确保AsyncOperation.OperationCompleted()在允许操作方法返回之前进行调用。

对于您的情况,如果邮件发送确实是一劳永逸的任务,则可以CreateUser在将其签名更改为之后执行以下操作async Task CreateUserAsync(ViewModel.VM_User user)

await Task.Run(() => Email("CreateUser", user).DeliverAsync());
Run Code Online (Sandbox Code Playgroud)

在你的行动中:

await new MailController().CreateUserAsync(user.userDetails);
Run Code Online (Sandbox Code Playgroud)

理想情况下,您不会使用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);
}
Run Code Online (Sandbox Code Playgroud)