Acknowledgment
Based on the recent comments on this article, I made some changes to it. Thank you for your attention.
Introduction
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
IAsyncResult
pattern where asynchronous operations require Begin
and End
methods. You are responsible to call Begin
method of that operation and somewhere else pool for the completion/cancellation status and finally call End
method 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?
async/await
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:
- The
doTheJob()
is called (this is our initial step). - The function executes synchronously until it reaches the
await
keyword in doSomethingASync()
where the asynchronous task is returned. - A new
Task
is 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 doTheJob()
. - The execution continues in the "anounymous-task" function until it reaches to “
return
” and continues in the "doTheJob()
" synchronously up to “return
”. - The execution of this function is done too and the
Task
which 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 async
way. 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
async
/await
and Task.Run
in 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 ConfigureAwait()
for”) - Your code is more readable, understandable and thus debug able.
What is the Difference with ContinueWith() in .NET 4.0?
Actually, the async
/await
is a replacement of their successor "ContinueWith()
" which was introduced in NET 4.0. Each approach has its pros and cons.
ContinueWith
is ignorance of sync context ContinueWith
pushes you to a new thread, even if the parent is already complete - Access
.Result
on a faulted Task
inside of ContinueWith
will re-throw Async
/await
will try to keep you on the same thread if they can
So upgrade to .NET 4.5 and use async
and await
…
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
async
operation 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! - Deadlock!
There are two approaches for that:
- Never use any kind of blocking waits and instead use
WhenAll
or When
functions to wait for a task to be completed and continue after completion. - Allow wrapped content to be executed in "
Context b
".
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!
await doSomeJobAsync(parameters).ConfigureAwait(false);
.....
Sometimes, it is really unnecessary to force execution continues in the caller context, for example it is an I/O operation which can be done in a background thread in other contexts so it is a good idea to use ConfigureAwait(false)
for those cases.
How Is It Possible to Wrap Traditional IAsyncResult Pattern into C# 5 Task?
You may be wondering what if I want to use an IAsyncResult
pair functions which are not provided in the new async
/await
pattern?
The APM pattern was introduced by having BeginOperation
and EndOperation
pair functions. The async
operation is started prior to a Begin
method call and the result is accessible according to an End
method call once the operation is done, otherwise you would stock in a waited lock for the async
operation to be finished.
Most of the .NET APIs are implemented in the new TAP pattern and you can recognize them easily according to this naming pattern from MSDN:
"Asynchronous methods in TAP include the Async suffix after the operation name; for example, GetAsync for a get operation. If you're adding a TAP method to a class that already contains that method name with the Async suffix, use the suffix TaskAsync instead. For example, if the class already has a GetAsync method, use the name GetTaskAsync".
So if you want to extend an IAsyncResult
pattern and replace them with a single TAP function call, you can use Task.Factory.FromAsync
to create your new behavior over those "Begin
" and "End
" method calls.
"FromAsync Creates a Task that represents a pair of begin and end methods that conform to the Asynchronous Programming Model pattern"
class Program
{
static void Main(string[] args)
{
Task<string> t = GetFileStringAsync(file_path);
try
{
Console.WriteLine(t.Result.Substring(0, Math.Min(500, t.Result.Length)));
}
catch (AggregateException ae)
{
Console.WriteLine(ae.InnerException.Message);
}
Console.ReadKey();
}
const int MAX_FILE_SIZE = 14000000;
public static Task<string> GetFileStringAsync(string path)
{
FileInfo fi = new FileInfo(path);
byte[] data = null;
data = new byte[fi.Length];
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read,
FileShare.Read, data.Length, true);
Task<int> task = Task<int>.Factory.FromAsync(fs.BeginRead, fs.EndRead,
data, 0, data.Length, null);
return task.ContinueWith((antecedent) =>
{
fs.Close();
if (antecedent.Result < data.Length)
Array.Resize(ref data, antecedent.Result);
return new UTF8Encoding().GetString(data);
});
}
}
More to Know
- Do not get confused by the returning type of any
async
function. When you call it using await
keyword, it can be considered like a normal function call. - If you want to use
await
keyword, your function has to be defined with async
keyword. - If you don't call
await
inside of your async
function, your async
function actually executes synchronously. - Use
Task.Delay()
instead of Thread.Sleep()
. - If you are developing a library or you are trying to use
async
/await
in ASP.NET for example, do not forget to call ConfigureAwait(false)
otherwise you definitely may introduce an unwanted deadlock.
And one more thing, if you need to have different await
calls which are not dependent on each other (assume them as independent I/O operations), it would be a good idea to await
for them all at once instead of awaiting them one-by-one:
int a = await doSomethingASync(10);
int b = await doSomethingASync(250);
int c = await doSomethingASync(670);
Total execution time would be 3+2+5 seconds which is 10 seconds. But these operations are not dependent to each other so the below code would have better performance and lower execution time:
Task<int>[] jobs = new Task<int>[3];
jobs[0] = doSomethingASync(10);
jobs[1] = doSomethingASync(250);
jobs[2] = doSomethingASync(670);
await Task.WhenAll(jobs);
In this pattern, the total execution time would be around 5 seconds (the most time consuming operation) and the rest would take advantage of asynchronous operation.
Point of Interest
In some cases, the amount of work required to complete the operation is less than the amount of work required to launch the operation asynchronously. Reading from a stream where the read operation can be satisfied by data that is already buffered in memory is an example of such a scenario. In such cases, the operation may complete synchronously, and may return a task that has already been completed.
Just keep it in mind.