Sometimes, we use new technologies while we are not aware of the bottlenecks and weird parts of that technology which eventually might trap us in bad situations like deadlocks which may be really hard to track. What exactly happens in Task-based Asynchronous Pattern (TAP), what are the improvements since .NET 4.0, how may we introduce a deadlock and how to prevent it?
Consider somewhere in your code, you need to do something asynchronous, what are the different ways to do it? Here are some of the patterns:
- Asynchronous Programming Model (APM): This model is also called
IAsyncResultpattern where asynchronous operations require
Endmethods. You are responsible to call
Beginmethod of that operation and somewhere else pool for the completion/cancellation status and finally call
Endmethod to retrieve the actual result of that operation. It is possible to define a callback function so when the operation is done, your callback function is called or you can block your execution until the result is ready. This pattern is not recommended in the new development, please keep it in mind.
- Event-based Asynchronous Pattern (EAP): This type of async programming was introduced in .NET 2.0 and is not recommended in the new development. In this model, you need to have event handler delegate types and
EventArg-derived types and in some scenarios, a worker thread to handle the async job and when it is finished, signal the parent about completion of the job.
- TAP model: We will discuss it in this article.
In the above patterns, you may easily end-up with lots of worker threads that are consuming your resources or in the worse case, you may face a hard time to manage sequence of events and execution process.
But, what about a way to somehow code synchronous but execute it asynchronous? Just leave the hard part to the compiler to make it happen for us?
What Does async/await Exactly Do?
Whenever you declare or see a function as
async, it means that this function is wait able and you can call it asynchronously using
await keyword. As soon as compiler sees the
await keyword, it does the magic for us and immediately returns to the calling function without the need to wait for the job to be completed and the execution would be able to continue its job.
It sounds great, isn’t it?
Here is the explanation:
doTheJob()is called (this is our initial step).
- The function executes synchronously until it reaches the
doSomethingASync()where the asynchronous task is returned.
- A new
Taskis created on-the-fly and starts to execute asynchronously and the continuation is scheduled at this point to run the remainder of the code in
- The execution continues in the “anounymous-task” function until it reaches to “
return” and continues in the “
doTheJob()” synchronously up to “
- The execution of this function is done too and the
Taskwhich is returned by
doTheJob()is completed and any possible continuations registered with it may now be scheduled.
It seems a little weird but it is actually simple to understand. as you may have already noticed, although your code seems to be very similar to synchronous programming, but in fact it is going to be executed in an
asyncway. You can simply await for a “time consuming” task and leave it there to be completed and schedule your execution once it is done!
What is the Benefit?
- Your synchronous style of coding would be executed asynchronously , that’s the magic (of course with the help of
Task.Runin this article for example)
- You won’t be worried anymore about lots of event pooling/checking, thread synchronizations and tough situations you experienced before just to handle multi-threaded asynchronous approaches.
Of course, you still have to understand asynchronous programming and how to synchronous any shared state, which means there’s still the potential for race conditions, deadlocks, starvation, etc. as you may find in this article. (See the “What is the
- Your code is more readable, understandable and thus debug able.
What is the Difference with ContinueWith() in .NET 4.0?
await is a replacement of their successor “
ContinueWith()” which was introduced in NET 4.0. Each approach has its pros and cons.
ContinueWithis ignorance of sync context
ContinueWithpushes you to a new thread, even if the parent is already complete
.Resulton a faulted
awaitwill try to keep you on the same thread if they can
So upgrade to .NET 4.5 and use
What is the ConfigureAwait() for?
It was introduced since .NET 4.5 because of some deadlock situation which may happen in accordance with a bad code structure. Consider the following diagram:
What happened? Did you get it?
- When you awaitened in
Fn2(), it actually moved the
f()execution into another context with its thread pool and the rest of
Fn2()was wrapped to be executed after the
f()call is done.
- The execution control returned to
Fn1()and continued until it reached out to a Blocking wait. Here is the point. (You actually blocked “Context a” execution process for another async operation to be completed)
- As soon as
asyncoperation in “
Context b” is completed, it tries to get access to “
Context a” just to inform it that “execution is done, let’s continue with the wrapped part”.
- But, “
Context a” is blocked before!
There are two approaches for that:
- Never use any kind of blocking waits and instead use
Whenfunctions to wait for a task to be completed and continue after completion.
- Allow wrapped content to be executed in “
For the second approach, you need to somehow configure the
await keyword to be aware of that! So you need to call
ConfigureAwait(false) so the compiler would know that the wrapped content must be executed in the
async context and there is no need to return back to calling context just to continue wrapped content!
continue reading in Codeproject :