Nowadays, Asynchronous programming is very popular with the help of the async and await keywords in C#.

Recently we were using Async Await keyword while writing unit test cases in our project and found various issues in using them correclty.

As users, we prefer applications which respond quickly and do not freeze when loading or processing data. While we are less patient with applications that keep us waiting. Even operating systems are becoming more responsive, and give you the option to terminate such misbehaving processes.

enter image description here

If you have been a developer for a while, it is very likely that you faced scenarios where your application became unresponsive. One such example is a data retrieval operation that usually completed in a few hundred milliseconds at the most, suddenly took several seconds because of server or network connectivity issues. Since the call was synchronous, the application did not respond to any user interaction during that time.

To understand why this happens, we must take a closer look at how the operating system communicates with applications. Whenever the OS needs to notify the application of something, be it the user clicking a button or wanting to close the application; it sends a message with all the information describing the action to the application. These messages are stored in a queue, waiting for the application to process them and react accordingly.

Each application with a graphical user interface (GUI) has a main message loop which continuously checks the contents of this queue. If there are any unprocessed messages in the queue, it takes out the first one and processes it. In a higher-level language such as C #, this usually results in invoking a corresponding event handler. The code in the event handler executes synchronously. Until it completes, none of the other messages in the queue are processed. If it takes too long, the application will appear to stop responding to user interaction.

Asynchronous programming using async and await keywords provides a simple way to avoid this problem with minimal code changes. For example, the following event handler synchronously downloads an HTTP resource:

    private void OnRequestDownload(object sender, RoutedEventArgs e)
{
    var request = HttpWebRequest.Create(_requestedUri);
    var response = request.GetResponse();
    // process the response
}

Here is an asynchronous version of the same code:

    private async void OnRequestDownload(object sender, RoutedEventArgs e)
{
    var request = HttpWebRequest.Create(_requestedUri);
    var response = await request.GetResponseAsync();
    // process the response
}

Only three changes were required:

  • The method signature changed from void to async void, indicating that the method is asynchronous, allowing us to use the await keyword in its body.
  • Instead of calling the synchronous GetResponse method, we are calling the asynchronous GetResponseAsync method. By convention, asynchronous method names usually have the Async postfix.
  • We added the await keyword before the asynchronous method call.

This changes the behavior of the event handler. Only a part of the method up to the GetResponseAsync call, executes synchronously. At that point, the execution of the event handler pauses and the application returns to processing the messages from the queue.

Meanwhile, the download operation continues in the background. Once it completes, it posts a new message to the queue. When the message loop processes it, the execution of the event handler method resumes from the GetResponseAsync call. First, its result of type Task is unwrapped to WebResponse and assigned to the response variable. Then, the rest of the method executes as expected.

Let us look at some of the most common pitfall examples.

Avoid Using Async Void

The signature of our asynchronous method in the code example we just saw was async void.

While this is appropriate for an event handler and the only way to write one, you should avoid this signature in all other cases. Instead, you should use async Task or async Task whenever possible, where T is the return type of your method.

As explained in the previous example, we need to call all asynchronous methods using the await keyword, e.g.:

DoSomeStuff(); // synchronous method
await DoSomeLengthyStuffAsync(); // long-running asynchronous method
DoSomeMoreStuff(); // another synchronous method

This allows the compiler to split the calling method at the point of the await keyword. The first part ends with the asynchronous method call; the second part starts with using its result if any, and continues from there on.

In order to use the await keyword on a method; its return type must be Task. This allows the compiler to trigger the continuation of our method, once the task completes. In other words, this will work as long as the asynchronous method’s signature is async Task. Had the signature been async void instead, we would have to call it without the await keyword:

DoSomeStuff(); // synchronous method
DoSomeLengthyStuffAsync(); // long-running asynchronous method
DoSomeMoreStuff(); // another synchronous method

The compiler would not complain though. Depending on the side effects of DoSomeLengthyStuffAsync, the code might even work correctly. However, there is one important difference between the two examples. In the first one, DoSomeMoreStuff will only be invoked after DoSomeLengthyStuffAsync completes. In the second one, DoSomeMoreStuff will be invoked immediately after DoSomeLengthyStuffAsync starts. Since in the latter case DoSomeLengthyStuffAsync and DoSomeMoreStuff run in parallel, race conditions might occur. If DoSomeMoreStuff depends on any of DoSomeLengthyStuffAsync’s side effects, these might or might not yet be available when DoSomeMoreStuff wants to use them. Such a bug can be difficult to fix, because it cannot be reproduced reliably. It can also occur only in production environment, where I/O operations are usually slower than in development environment.

Conclusion:

Even though asynchronous programming with C# async and await seems simple enough once you get used to it, there are still pitfalls to be aware of. The most common one is improper use of async void, which you can easily overlook and the compiler will not warn you about it either. This can introduce subtle and hard to reproduce bugs in your code that will cost you a lot of time to fix.

Your comment

*

*