C# 5.0 introduced the async/await keyword to simplify the asynchronous programming model and remove syntactic sugar, which is Net4.0’s Task + state machine. It’s actually pretty easy to use Task for asynchronous programming, but with the introduction of new syntactic candy, it’s inevitable to try it, but it’s not as simple as you’d expect. Here are some examples of how ASP.NET applications can be used in real life, including exception catching, deadlocks, and application crashes.

Exception handling

Async methods have three return types: void, Task, and Task

async void

Methods declared in this way cannot catch exceptions, so the following try and catch methods are useless.

private static async void ThrowExceptionAsync()

{

await Task.Delay(1000);

Throw a new Exception;

}



public static async void CatchAsyncVoidException()

{

try

{

ThrowExceptionAsync();

}

catch (Exception ex)

{

throw ex;

}

}

Copy the code
Async Task or async Task

The method exception information declared in these two ways will be contained in the Task property, but only if you wait with await inside the try.

private static async Task ThrowExceptionAsync()

{

await Task.Delay(1000);

Throw a new Exception;

}



public static async Task CatchAsyncTaskException()

{

try

{

await ThrowExceptionAsync();

}

catch (Exception ex)

{

throw ex;

}

}

Copy the code
TaskScheduler.UnobservedTaskException

Does not capture the Task of exception information can set the Global TaskScheduler. UnobservedTaskException to record the error log, in Global. Asax add the following code:

void Application_Start(object sender, EventArgs e)

{

// The code that runs when the application starts

TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskExceptionException;

}



void TaskScheduler_UnobservedTaskExceptionException(object sender, UnobservedTaskExceptionEventArgs e)

{

if (e.Exception ! = null)

{

// do something

}

}

Copy the code

Synchronization context

Asynchronous programming is necessarily about the use of threads, threads have a concept of synchronous context, which I personally think is the most worrying problem async/await encounters. We might try to use async/await in an existing project, but old code is synchronous, and if we call a method declared async, deadlock and application crashes can happen accidentally.

Note: console program and. Net Core programs will not encounter this problem; they do not need to synchronize context.

A deadlock
private static async Task XXXAsync()

{

await Task.Delay(1000);

// some code

}



public static void Test()

{

var task = XXXAsync();

task.Wait();

}

Copy the code

The above code perfectly implements deadlocks. By default, when waiting () for an unfinished Task, the current thread context is captured and used to resume execution of the method when the Task completes. When an await execution is complete within an async method, it tries to get the rest of the execution method in the context where the caller’s thread is, but that context already contains a thread waiting for the async method to complete. And then they wait for each other, and then they don’t and then they die there.

The solution to deadlock problems is to add ConfigureAwait(false)

// await Task.Delay(1000);

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

Copy the code

When await is complete, it tries to execute the rest of the async method in the context on the thread pool, so there is no deadlock.

Application crash

The IIS application pool always crashes in the test environment. What is the cause? At the time, we were very confused about this problem. The code didn’t look obviously wrong, and we tried to fool ourselves that it was the environment itself, but in fact it was poison in the code. Through all kinds of data review and testing, it can be concluded that the synchronous code is calling the asynchronous code, the synchronization context caused the problem.

If you call an async method. If you await with await, the current thread is immediately released back to the thread pool and the thread’s context information is saved. After calling async, the code will continue to execute, and the current thread will be released back to the thread pool, and the thread’s context information will not be saved. When an asynchronous task in async completes, a thread is fetched from the thread pool to continue executing the rest of the code, and the context information of the original caller’s thread is retrieved (if the original caller’s thread is not released back into the thread pool, the context information can be retrieved). So the question becomes, the caller if it did not use await and free threads in the thread pool, context information is to keep down, because there is no won’t get it, will throw an exception at this moment The object reference not set to an instance of an object, through testing the exception information is not necessarily always appear, the reason and the release of thread, No exception is thrown if context information exists for the caller’s thread. The exception error message follows, and this exception will eventually cause the application assembly to stop.

In the System. Web. ThreadContext. AssociateWithCurrentThread (Boolean setImpersonationContext)

In the System. Web. HttpApplication. OnThreadEnterPrivate (Boolean setImpersonationContext)

In the System. Web. LegacyAspNetSynchronizationContext. CallCallbackPossiblyUnderLock (SendOrPostCallback callback, the Object state)

In the System. Web. LegacyAspNetSynchronizationContext. CallCallback (SendOrPostCallback callback, the Object state)

In the System. Web. LegacyAspNetSynchronizationContext. Post (SendOrPostCallback callback, the Object state)

In the System. The Threading. Tasks. SynchronizationContextAwaitTaskContinuation. PostAction (Object state)

In the System. The Threading. Tasks. AwaitTaskContinuation. RunCallback (ContextCallback callback, the Object state, Task & currentTask)

The end of the stack trace in the previous location where the exception was thrown --

In the System. The Threading. Tasks. AwaitTaskContinuation. < > c. < ThrowAsyncIfNecessary > b__18_0 Object (s)

In the System. The Threading. QueueUserWorkItemCallback. WaitCallback_Context (Object state)

In the System. The Threading. -- an optional ExecutionContext. RunInternal (-- an optional ExecutionContext -- an optional ExecutionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)

In the System. The Threading. -- an optional ExecutionContext. Run (-- an optional ExecutionContext -- an optional ExecutionContext, ContextCallback callback, the Object state, Boolean preserveSyncCtx)

In the System. The Threading. QueueUserWorkItemCallback. System. Threading. IThreadPoolWorkItem. ExecuteWorkItem ()

In the System. The Threading. ThreadPoolWorkQueue. Dispatch ()

In the System. The Threading. _ThreadPoolWaitCallback. PerformWaitCallback ()

Copy the code

What can we do to solve the above exception? ConfigureAwait(false), ConfigureAwait(false) on Task. This setting means that the context of the calling thread is not read when the asynchronous Task in async completes. Instead, it executes the remainder of the Async method below on the thread pool.

public static Task XXXAsync()

{

await Task.Run(() =>

{

// some code



}).ConfigureAwait(false);

}

Copy the code

conclusion

  1. Try not to mix synchronous and asynchronous code.
  2. Use ConfigureAwait (false);

Reference links: