NowDays, In .NET world asynchronous programming went on to a next level and became more popular with the help of new keywords i.e async and await 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 as the call was synchronous, the app did not responded to any user interaction during that period of 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 get completes, none of the other messages in the queue will get processed. Now, If it takes too long, the application will appear to stop responding to user interaction.

In asynchronous programming using of these defined keyword async and await provides a better and simple approach to avoid such a problem with minimal code reflection. For example, the following event handler method synchronously downloads an HTTP resource:

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

Now, Here is an asynchronous version of the above method:

    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 which will allow us to use the await keyword in its body.
  • Instead of following the old way of calling the synchronous GetResponse method, we are now calling the asynchronous GetResponseAsync method. By convention, asynchronous method names always have the postfix as Async .
  • We have added await keyword just before the asynchronous GetResponseAsync method call.

This changes the behavior of the event handler. Now with these only a part of the code in the method up to the GetResponseAsync call, executes synchronously. At that point, the execution of the event handler will get pause and the application will return to process the other messages from the queue.

Meanwhile, the download operation continues in the background. Once it completes, it will post a new message to the queue. Now as soon as 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 Return Type Async Void

In the above example we saw the signature of our asynchronous method was async void.

While this is appropriate for an event handler and the only way to write one, But 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 above previous example, we need to use await keyword to call all asynchronous methods. e.g.:

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

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

In order to use the await keyword on a any method; Make sure its return type must be Task. This will always 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 type is async Task. Had the method 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 bad 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# using async and await keywords seems simple enough once you get used to it, but there are still pitfalls to be aware of. The most common one which is not able to use of async void correctly, 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 which will then cost you a extra time to fix.