ASP. NET async and await

  • 2021-10-24 19:22:03
  • OfStack

C # 5.0 introduces the async/await keyword to simplify the asynchronous programming model, and the Task + state machine of Net 4.0 is thrown away. In fact, it is quite simple to use Task in dealing with asynchronous programming. However, since a new syntax sugar has been introduced, it is inevitable to try 1, but it is not as simple as imagined in use. The following is a summary of the actual use of ASP. NET application, including exception capture, deadlock and application crash. If you don't pay attention to it, you may fall into a pit.

Exception trapping

The async method has three return types: void, Task, and Task

async void

The method declared in this way can't catch exceptions using catch, so try and catch of the following code are useless.


private static async void ThrowExceptionAsync()
{
 await Task.Delay(1000);
 throw new Exception(" Throw an exception for fun ");
}
public static async void CatchAsyncVoidException()
{
 try
 {
  ThrowExceptionAsync();
 }
 catch (Exception ex)
 {
 throw ex;
 }
}

async Task or async Task

The method exception information declared in both ways will be included in the Task attribute, but the premise is to use await wait in try.


private static async Task ThrowExceptionAsync()
{
 await Task.Delay(1000);
 throw new Exception(" Throw an exception for fun ");
}
public static async Task CatchAsyncTaskException()
{
 try
 {
   await ThrowExceptionAsync();
 }
 catch (Exception ex)
 {
 throw ex;
 }
}
TaskScheduler.UnobservedTaskException

Uncaptured Task exception information can be logged by setting the global TaskScheduler. UnobservedTaskException, adding the following code to Global. asax:


void Application_Start(object sender, EventArgs e)
{
 //  Code that runs when the application starts 
 TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskExceptionException;
}
void TaskScheduler_UnobservedTaskExceptionException(object sender, UnobservedTaskExceptionEventArgs e)
{
 if (e.Exception != null)
 {
 // do something
 }
}

Synchronization context

Asynchronous programming must be about the use of threads. Threads have a concept of synchronization context. Personally, I think thread synchronization context is the most worrying problem encountered by async/await. We might want to try to use async/await in existing project development, but the old code is synchronous, so if a method declared async is called, deadlock and application crash problem 1 may occur accidentally.

Note: Console programs and. Net Core programs will not encounter this problem, they do not require synchronization context.

Deadlock


private static async Task XXXAsync()
{
 await Task.Delay(1000);
  // some code
}
public static void Test()
{
 var task = XXXAsync();
 task.Wait();
}

The above code perfectly realizes deadlock. By default, when Wait () does not complete Task, the current thread context is captured and used to resume method execution when Task completes. When the execution of await within the async method completes, it attempts to get the remainder of the execution method in the context of the caller thread, but that context already contains 1 thread that is waiting for the async method to complete. Then they wait for each other, and then there is no then, and die there.

The solution to the deadlock problem is to add ConfigureAwait (false)


// await Task.Delay(1000);
await Task.Delay(1000).ConfigureAwait(false); //  Resolve deadlock 

When await waits to complete, it attempts to execute the remainder of the async method in the thread pool context, so there is no deadlock.

Application crash

The test environment always finds that the IIS application pool always crashes. What is the reason? At that time, we were also very confused about this problem. The code didn't seem to have any obvious problems. Trying to deceive ourselves should be the problem of the environment itself, but in fact it was poisoned in the code. Through all kinds of data access and testing, we can basically conclude that synchronous code calls asynchronous code and synchronous context causes problems.

If 1 async method is called. If await is used to wait, the current thread is immediately released back into the thread pool, and the context information of the thread is saved. If you don't use await (async void method, you can't use await). After calling async method, the code will continue to execute. After execution, the current thread will be released back to the thread pool, and the context information of the thread will not be saved. When the asynchronous task in async is executed, one thread will be obtained from the thread pool to continue executing the remaining code, and the context information of the thread of the original caller will be obtained at the same time (if the thread of the original caller is not released back to the thread pool, the context information can be obtained). Then the problem comes, If the caller did not use await and the thread was released back into the thread pool, Because the context information is not preserved, it can't be obtained. At this time, an exception will be thrown. The object reference is not set to the instance of the object. After testing, this exception information does not appear every time. The reason is related to the release of the thread. If the context information of the thread that the caller has exists, it will not throw an exception. The exception error message is as follows, and this exception will eventually cause the application assembly to stop.


 In  System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
   In  System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
   In  System.Web.LegacyAspNetSynchronizationContext.CallCallbackPossiblyUnderLock(SendOrPostCallback callback, Object state)
   In  System.Web.LegacyAspNetSynchronizationContext.CallCallback(SendOrPostCallback callback, Object state)
   In  System.Web.LegacyAspNetSynchronizationContext.Post(SendOrPostCallback callback, Object state)
   In  System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.PostAction(Object state)
   In  System.Threading.Tasks.AwaitTaskContinuation.RunCallback(ContextCallback callback, Object state, Task& currentTask)
---  On which the exception is thrown 1 The end of the stack trace in the position  ---
   In  System.Threading.Tasks.AwaitTaskContinuation.<>c.<ThrowAsyncIfNecessary>b__18_0(Object s)
   In  System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
   In  System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   In  System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   In  System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   In  System.Threading.ThreadPoolWorkQueue.Dispatch()
   In  System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

What can we do to solve the above anomalies? Still ConfigureAwait (false), add ConfigureAwait (false) to Task. This setting means that when the asynchronous task in async completes, the context information of the original thread calling it at that time is not read, but the rest of the async method is executed in the context of the thread pool.


public static Task XXXAsync()
{
 await Task.Run(() =>
 {
 // some code
 }).ConfigureAwait(false);
}

Summarize


Related articles: