Error: An asynchronous module or handler completed while an asynchronous operation was still pending

后端 未结 2 1481
说谎
说谎 2021-01-15 19:26

I have a Controller Action method to Save user Details like below.

public async Task SaveUser(ViewModel.VM_CreateUser user)
{
    var res         


        
2条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2021-01-15 19:49

    This is because async methods track their completion, even async void. They do this by registering with the SynchronizationContext when starting and marking the operation complete when returning. ASP.NET tracks all created operations and requires them to all be completed before your action returns, otherwise it returns an error to the HTTP client. If you need to run a “fire and forget” method, you must manually avoid launching it on the ASP.NET SynchronizationContext. For example, await Task.Run(() => CallFireAndForget()) (await so that you wait for the synchronous portion to run on the thread pool. This works because CallFireAndForget() is async void. To fire a method which returns a Task as fire-and-forget, you have to explicitly avoid returning the Task to Task.Run() like this: await Task.Run(() => { CallFireAndForgetAsync(); })).

    Another way to get the same error message to show up should be to write this in an asynchronous action:

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

    If you use the AsyncOperation API, you have to ensure you call AsyncOperation.OperationCompleted() prior to allowing the action method to return.

    In your case, if the mail sending is really intended to be a fire-and-forget task, you can do the following inside of CreateUser after changing its signature to async Task CreateUserAsync(ViewModel.VM_User user):

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

    and in your action:

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

    Ideally, you wouldn’t use Task.Run() for something like this. Instead, you can temporarily replace the current SynchronizationContext instead (NOTE WELL: Never await inside the try{}—instead, if you need to, store the Task in a local and await it after restoring the original SynchronizationContext (it may be safer to use a primitive such as SynchronizationContextSwitcher.NoContext() (source) which does the right thing)):

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

提交回复
热议问题