C# 5.0 introduced the async and await keywords. These keywords let you write asynchronous code that has the same structure and simplicity as synchronous code, as well as eliminating the “plumbing” of asynchronous programming.

Contents

Awaiting

The await keyword simplifies the attaching of C# Task continuations.

Starting with a basic scenario, the compiler expands:

1 var result = await expression ; 2 statement ( s );

into something functionally similar to:

1 var awaiter = expression . GetAwaiter (); 2 awaiter . OnCompleted (() => 3 { 4 var result = awaiter . GetResult (); 5 statement ( s ); 6 });

The compiler also emits code to short-circuit the continuation in case of synchronous completion.

Awaiting in a UI

To demonstrate the above idea, I made a small WPF application that fetches data from the URL given in the textbox. You can find the complete project here.

1 using System ; 2 using System.Net.Http ; 3 using System.Threading.Tasks ; 4 using System.Windows ; 5 6 namespace WpfApp 7 { 8 public partial class MainWindow : Window 9 { 10 public MainWindow () 11 { 12 InitializeComponent (); 13 } 14 15 private void Button_Click ( object sender , RoutedEventArgs e ) 16 { 17 var url = textBox . Text ; 18 var isValidUrl = Uri . TryCreate ( url , UriKind . Absolute , out _ ); 19 if (! isValidUrl ) 20 { 21 textBlock . Text = "Given url is not valid." ; 22 return ; 23 } 24 25 var currentContext = TaskScheduler . FromCurrentSynchronizationContext (); 26 var httpClient = new HttpClient (); 27 28 var responseTask = httpClient . GetAsync ( url ); 29 //First continuation start 30 responseTask . ContinueWith ( r => { 31 try 32 { 33 var response = r . Result ; 34 response . EnsureSuccessStatusCode (); 35 36 var dataTask = response . Content . ReadAsStringAsync (); 37 //Second continuation start 38 dataTask . ContinueWith ( d => { 39 textBlock . Text = d . Result ; 40 }, currentContext ); 41 //Second continuation end 42 } 43 catch ( Exception ex ) 44 { 45 textBlock . Text = ex . Message ; 46 } 47 }); 48 //First continuation ends 49 } 50 } 51 }

Here responseTask.ContinueWith what simply does is that it callback the action, defined as the lambda expression, after the responseTask operation is finished.

Callbacks are not all bad; they worked — they still do. But, what happens if we have a callback inside a callback, inside a callback — you get the point. It gets messy and unmaintainable really quick.

The problem described above is named “callback hell”. Which you can see a bit in the above example.

To overcome the above problem which is more related to code readability, C# introduced the async await keyword. Let’s try it.

Async Await Keyword

A very common thing to first try out when you encounter the asynchronous method in .NET is to simply mark your parent method with the async keyword. Let’s go ahead and try that.

Also, let’s remove all the continuation from the code and directly use the .Result property to access the result from our asynchronous task operation.

Let’s see how that affects our application.

1 using System ; 2 using System.Net.Http ; 3 using System.Windows ; 4 5 namespace WpfApp 6 { 7 public partial class MainWindow : Window 8 { 9 public MainWindow () 10 { 11 InitializeComponent (); 12 } 13 14 private async void Button_Click ( object sender , RoutedEventArgs e ) 15 { 16 var url = textBox . Text ; 17 var isValidUrl = Uri . TryCreate ( url , UriKind . Absolute , out _ ); 18 if (! isValidUrl ) 19 { 20 textBlock . Text = "Given url is not valid." ; 21 return ; 22 } 23 24 var httpClient = new HttpClient (); 25 var response = httpClient . GetAsync ( url ). Result ; 26 try 27 { 28 29 response . EnsureSuccessStatusCode (); 30 var data = response . Content . ReadAsStringAsync (). Result ; 31 textBlock . Text = data ; 32 } 33 catch ( Exception ex ) 34 { 35 textBlock . Text = ex . Message ; 36 } 37 } 38 } 39 }

You’ll notice here that I can mark my Button_Click handler as async. And if you run the application and see if this affected the performance of our application, you’ll quickly notice that the application UI gets locks up. Let’s jump into the code and discuss about why this is still not an asynchronous operation.

As you see here, Visual Studio will tell us that this method is marked as async, but it lacks the await keyword. So the code inside this method will still run synchronously, and that’s a big problem because we want to leverage the asynchronous principles.

Why is this a problem?

Now, in the above code you may have seen the following line:

1 var response = httpClient . GetAsync ( url ). Result ;

Here GetAsync returns a task of an HttpResponseMessage, a task is a representation of our asynchronous operation. This asynchronous operation happens on a different thread.

So if we call .Result , which is one of the first things that people try to get the Result out of their asynchronous operation, this is actually a really bad idea.

It will actually block the thread until this Result is available, so this is problematic because this means that code will run synchronously.

Actually, what we need to do is to make sure that whenever we encounter the async keyword, we also have the await keyword inside that same method. Like this,

1 var response = await httpClient . GetAsync ( url );

The await keyword is a way for us to indicate that we want to get the Result out of this asynchronous operation only once the data is available without blocking the current thread. So the above code gives us the HttpResponseMessage.

Also,

While reading the content from the response you’ll find that ReadAsString is also an asynchronous operation, and it also hints us here that we need to await this as well.

1 var data = await response . Content . ReadAsStringAsync ();

We could, of course, say ReadAsStringAsync and then call the Result property, but this would block again and make this code run synchronously, and in a lot of cases, calling .Result or .Wait will, in fact, deadlock the application. So avoid calling .Result or .Wait .

Let’s see the final result with all the changes we did,

1 using System ; 2 using System.Net.Http ; 3 using System.Windows ; 4 5 namespace WpfApp 6 { 7 public partial class MainWindow : Window 8 { 9 public MainWindow () 10 { 11 InitializeComponent (); 12 } 13 14 private async void Button_Click ( object sender , RoutedEventArgs e ) 15 { 16 var url = textBox . Text ; 17 var isValidUrl = Uri . TryCreate ( url , UriKind . Absolute , out _ ); 18 if (! isValidUrl ) 19 { 20 textBlock . Text = "Given url is not valid." ; 21 return ; 22 } 23 24 var httpClient = new HttpClient (); 25 var response = await httpClient . GetAsync ( url ); 26 try 27 { 28 29 response . EnsureSuccessStatusCode (); 30 var data = await response . Content . ReadAsStringAsync (); 31 textBlock . Text = data ; 32 } 33 catch ( Exception ex ) 34 { 35 textBlock . Text = ex . Message ; 36 } 37 } 38 } 39 }

So,

The await keyword, allows us to retrieve the result out of our asynchronous operation when that’s available. It also makes sure that there are no exceptions or problems with the task that it’s currently awaiting. So not only is the await keyword a great way for us to get the Result out of the asynchronous operation. It also validates the current operation.

And,

What it’s also doing is introducing continuation, as we’ve mentioned earlier, the await keyword does the same behind the scene and puts all the code beneath it inside the continuation.

Also,

You can await the result of an async method that returns a Task because the method returns a Task, not because it’s async. That means you can also await the result of a non-async method that returns a Task:

1 public async Task NewStuffAsync () 2 { 3 // Use await and have fun with the new stuff. 4 await ... 5 } 6 7 public Task MyOldTaskParallelLibraryCode () 8 { 9 // Note that this is not an async method, so we can't use await in here. 10 ... 11 } 12 13 public async Task ComposeAsync () 14 { 15 // We can await Tasks, regardless of where they come from. 16 await NewStuffAsync (); 17 await MyOldTaskParallelLibraryCode (); 18 }

Application Of Asynchronous Principles Accross .Net

Asynchronous principles are suited for any type of I/O operations. As we do in this case, we interact with an API over the web, but it could also be reading and writing from disk or memory or do things like database operations. In our case here, we’re fetching some data from our API using the GetAsync method on our HttpClient.

The asynchronous principles that we talk about in our applications are not only meant for Windows applications or mobile applications. We can also apply the same principle to the server-side code in ASP.NET.

Awaiting In ASP.NET

Let’s see an example:

1 using System ; 2 using System.Net.Http ; 3 using System.Threading.Tasks ; 4 using System.Web.Http ; 5 6 namespace WebApplication.Controllers 7 { 8 public class TestController : ApiController 9 { 10 // GET: api/Test 11 public async Task < IHttpActionResult > Get ( string url ) 12 { 13 var isValidUrl = Uri . TryCreate ( url , UriKind . Absolute , out _ ); 14 if (! isValidUrl ) 15 { 16 return BadRequest ( "Given url is not valid." ); 17 } 18 19 var httpClient = new HttpClient (); 20 var response = await httpClient . GetAsync ( url ). ConfigureAwait ( false ); 21 try 22 { 23 24 response . EnsureSuccessStatusCode (); 25 var data = await response . Content . ReadAsStringAsync (). ConfigureAwait ( false ); 26 return Ok ( data ); 27 } 28 catch ( Exception ex ) 29 { 30 return BadRequest ( ex . Message ); 31 } 32 } 33 } 34 }

Here is a test controller inside a web project that’s allowing us to pretty much do the same thing that we do in our Windows application. However, we have a minor difference here:

1 . ConfigureAwait ( false )

both of the task call this method in the end.

Why?

Avoiding Excessive Bouncing

When you await an async method, then it will capture the current “context” and later when the task completes, it will execute the remainder of the async method on a “context” that was captured before the “await” returned.

What Exactly Is That “Context”?

Simple answer:

If you’re on a UI thread, then it’s a UI context. If you’re responding to an ASP.NET request, then it’s an ASP.NET request context. Otherwise, it’s usually a thread pool context.

Complex answer:

If SynchronizationContext.Current is not null, then it’s the current SynchronizationContext. (UI and ASP.NET request contexts are SynchronizationContext contexts).

Otherwise, it’s the current TaskScheduler (TaskScheduler.Default is the thread pool context). What does this mean in the real world? For one thing, capturing (and restoring) the UI/ASP.NET context is done transparently:

Why Capturing the Current Context is Needed?

One example of when it’s necessary is in WPF apps. Imagine we delegate some kind of operation to another thread, and we want to use the result for setting a text box Text property. But the problem is, in this framework, only the thread that creates the UI element has the right to change its property. If we try to change a UI element from another thread, we get the InvalidOperationException error.

Most of the time, you don’t need to sync back to the “main” context. You want to use ConfigureAwait(false) whenever the rest of that async method does not depend on the current context.

Also,

Some frameworks depending on their internal implementation don’t need SynchronizationContext. Asp.Net Core is one such framework. Read more about it here. In short in such frameworks there might be no need for ConfigureAwait(false).

The Benefit Of Using Async And Await Inside ASP.NET?

Now what’s interesting here is that this is not making the client-side app asynchronous.

So, what’s the benefit?

The benefit of using async and await inside ASP.NET is to relieve IIS or the web server that you were using so that it can go ahead and work with other requests as your data is being loaded from disk, the database, or from another API. The primary benefit of asynchronous code on the server-side is scalability.

So as you notice here, the asynchronous principles are really powerful no matter if we are working in ASP.NET, in Windows, or any type of .NET applications.

In the next post Task Parallelism In C#, we will look at different methods to achieve parallelism using Tasks in C#.

Furthur Reading