Writing asynchronous code with .NET

Dominic Burford
7 min readJun 10, 2019

I’ve been writing asychronous code with the .NET Framework for several years now, and find that the .NET Framework makes a good job of hiding the underlying conceptual details. The concepts are pretty straight forward if you undedrstand how asynchronicity works. As I’ve found over the years though, these concepts are not always well understood or applied by less experienced developers. By less experienced, I don’t always mean junior developers. I’ve come across senior developers who have struggled with asynchronous code too.

I’ve helped several developers fix issues with their code that have been due to mis-understandings with asynchronicity, and I’ve found issues with their code during code reviews that have highlighted basic mis-understandings with implementing asynchronous code using the .NET Framework.

In this article I want to go through the basics of writing asynchronous code using the .NET Framework. I’ll use C# to illustrate all examples, but conceptually the code will work the same when transposed to VB.NET or any other .NET language. I’ll use examples from our ASP.NET Web API services code base which makes extensive use of asynchronicity to give performant and responsive code. Our mobile apps all rely on these services for delivering functionality to the end user’s device. It is therefore incumbent on our apps to be highly responsive and performant. I have therefore made all these services asynchronous to effect these requirements. I may follow this article up in the future with more advanced scenarios, but for now, I will stick to the basics.

What is Asynchronous Programming?

Let’s start with some basic understanding of asynchronous programming. Most code gets executed in a sequential manner i.e.

- execute line 1
- execute line 2
- execute line 3

We have 3 lines of code that each execute some command, and they each run one after the other i.e. “execute line 1” is executed first. When this has finished execution then “execute line 2” gets executed. When this has finished executing then “execute line 3” is executed. These commands are run sequentially, one after another. This can also be referred to as synchronous code. The next line of code can only be executed when the previous line of code has completed.

Hide Copy Code

var myList = new List<string>();
myList.Add("item1");
myList.Add("item2");
myList.Add("item3");
myList.Remove("item1");

A trivial example could be the code above. The first line creates a string list called myList. When this has completed the next 3 lines then add items to the string list (item1, item2 and item3). Finally, we remove item1 from the list. These lines of code are executed one after the other in a sequential (synchronous) manner.

When code is executed sequentially like this, one command after the other, we say that it has been executed synchronously.

We need to write our code differently when we interact with any kind of I/O device such as a file, a network or database. The same applies when we execute any CPU bound operations such as rendering high-intensity graphics during a game. We cannot make any guarantees about how quickly the device or operation may respond to our request, so we need to factor in waiting time when making requests to I/O devices or CPU intensive requests.

An anology may be making a telephone call to book an appointment to have your car serviced. Immediately after making your booking you need to then write down the date and time of the booking. You may get straight to the front of the telephone queue if you’re lucky. Alternatively, you may find you are further down the telephone queue and have to wait to get through to the garage. Either way, you cannot write down the date and time of the booking until you have gotten through to the garage.

In this scernario you don’t know exactly when you can write down the date and time of the booking as you may have to wait to get through to the garage.

And this is exactly how asynchronous code works.

When your code accesses I/O devices such as accessing a file, network or database (or makes a request to a CPU intensive operation) you cannot guarantee when your request will be serviced. For example, if you are accessing a database, there may be latency on the network, it may be hosted on legacy hardware, the record you are accessing may be locked and so on. Any one of these will affect the timeliness (or otherwise) of the response to your request.

If your network or database is busy and under extreme load, any request sent over it will be slower than requests made during less busy times. So it should be obvious that executing a command that relies on an I/O device immediately after submitting a request to that I/O device is likely to fail, as you may not have received any response from the I/O device.

Example
- connect to database
- fetch records from database
- close database connection

If you were to execute the above code synchronously, you could easily run into the situation where you are trying to fetch the database records before you have fully connected to the database. This would fail resulting in an exception being thrown. What you instead need to do is attempt to connect to the database, and ONLY when that has succeeded should you attempt to fetch the records from the database. Once you have fetched the records from the database, then you can close the database connection.

This is exactly how asynchronous code works. We can rewrite the above pseudo-code asynchronously.
- connect to the database
- wait for connection to database to be established
- once connected to the database fetch records from database
- close database connection

The two sets of pseudo-code look very similar, with the key difference being that the latter waits for the connection to the database to be established BEFORE making any attempts to fetch records from the database.

Hopefully by this point the goals of asynchronous programming should be clear. The goal of asynchronous programming is to allow our code to wait for responses from I/O or CPU bound recources such as files, networks, databases etc.

Asynchronous programming with C#

Now that we understand the principles and goals behind asynchronous programming, how do we write asynchronous code in C#?

Asynchronous programming is implemented in C# using Task and Task<t>. These model asynchronous operations, and are supported by the keywords async and await. Task and Task<t> are return values from asynchronous operations that can be awaited.

Here’s a function that POSTs data to a RESTful endpoint, and does so asynchronously. For the purposes of simplicity I have removed all authentication etc from the code samples I will use.

Hide Copy Code

public async Task<HttpResponseMessage> PostData(string url, HttpContent content)
{
using (var client = new HttpClient())
{
return await client.PostAsync(new Uri(url), content);
}
}

Things to note.
- The method returns a Task of type HttpResponseMessage to the calling program i.e. the method is returning an instance of HttpResponseMessage (e.g. an HTTP 200 if the method was successful).
- The async keyword in the method signature is required because the method invokes the PostAsync() method in the method body i.e. the method needs to await the response from the RESTful API before the response can be handed back to the calling program.

To call this function we write the following code.

Hide Copy Code

var response = await PostData(url, content);

The calling code (above) needs to await the response from the PostData() method and does so using the await keyword. Whenever you invoke an asynchronous method such as PostAsync(), you need to await the response. The two keywords go hand in hand. Asynchronous methods need to be awaited when they are invoked.

Here’s another RESTful API method that fetches some data from a RESTful endpoint. The RESTful endpoint returns data in the form of a serialised JSON string (which the calling program will then de-serialise back into an object).

Hide Copy Code

public async Task<string> GetData(string url)
{
using (var client = new HttpClient())
{
using (var r = await client.GetAsync(new Uri(url)))
{
string result = await r.Content.ReadAsStringAsync();
return result;
}
}
}

Things to note.
- The method returns a Task of type string to the calling program (the JSON serialised response from the RESTful endpoint).
- The async keyword in the method signature is required because the method invokes the GetAsync() and ReadAsStringAsync() methods in the method body.

To call this function we write the following code.

Hide Copy Code

string response = await GetData(url);
if (!string.IsNullOrEmpty(response))
{
//de-serialise the string into an object here
}

The calling code (above) needs to await the response from the GetData() method and does so using the await keyword.

Key takeaways

- Async code be can used for both I/O bound as well as CPU bound code
- Async code uses Task and Task<t> which represent asynchronous methods and are the return values from asynchronousmethods (as we saw in the PostData() and GetData() methods)
- The async keyword turns a method into an async method which then allows you to use the await keyword in its method body (as we saw in the PostData() and GetData() methods).
- Invoking an asynchronous method using the await keyword suspends the calling program and yields control back to the calling program until the awaited task is complete
- The await keyword can only be used within an async method

This is the first in what will hopefully be a series of articles I intend to write on asynchronous programming. I will cover other areas of asynchronous programming in future articles (giving tips, advice, advanced scenarios and even Javascript). Hopefully for now, this has given a taster of how to implement the basics of asynchronous programming with C#. Watch this space for further articles.

--

--

Dominic Burford
Dominic Burford

Written by Dominic Burford

A father, cyclist, vegetarian, atheist, geek and multiple award winning technical author. Loves real ale, fine wine and good music. All round decent chap.

No responses yet