Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#6.0

async/await - What You Should Know (Updated!)

4.66/5 (42 votes)
25 Nov 2015CPOL8 min read 85.2K   839  
Simply understand what happens when you use async/await keywords, what are the benefits and how to prevent deadlocks

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?

Image 1

Here is the explanation:

  1. The doTheJob() is called (this is our initial step).
  2. The function executes synchronously until it reaches the await keyword in doSomethingASync() where the asynchronous task is returned.
  3. 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().
  4. The execution continues in the "anounymous-task" function until it reaches to “return” and continues in the "doTheJob()" synchronously up to “return”.
  5. 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:

Image 2

What happened? Did you get it?

  1. 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.
  2. 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)
  3. 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".
  4. But, "Context a" is blocked before!
  5. 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!

C#
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"

C#
// borrowed from MSDN:
class Program
{
    static void Main(string[] args)
    {
        Task<string> t = GetFileStringAsync(file_path);

        // Do some other work:
        // ...

        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> returns the number of bytes read
        Task<int> task = Task<int>.Factory.FromAsync(fs.BeginRead, fs.EndRead, 
                                        data, 0, data.Length, null);

        // It is possible to do other work here while waiting
        // for the antecedent task to complete.
        // ...

        // Add the continuation, which returns a Task<string>. 
        return task.ContinueWith((antecedent) =>
        {
            fs.Close();

            // If we did not receive the entire file, the end of the
            // data buffer will contain garbage.
            if (antecedent.Result < data.Length)
                Array.Resize(ref data, antecedent.Result);

            // Will be returned in the Result property of the Task<string>
            // at some future point after the asynchronous file I/O operation completes.
            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:

C#
int a = await doSomethingASync(10);       // Executes in 3 seconds
int b = await doSomethingASync(250);      // Executes in 2 seconds
int c = await doSomethingASync(670);      // Executes in 5 seconds

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:

C#
Task<int>[] jobs = new Task<int>[3];
jobs[0] = doSomethingASync(10);       // Executes in 3 seconds
jobs[1] = doSomethingASync(250);      // Executes in 2 seconds
jobs[2] = doSomethingASync(670);      // Executes in 5 seconds

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)