Using Async and Await in C# (Part 1)
In programming, there are many use cases for doing things in an asynchronous way. In almost all cases, it comes down to executing an operation that may take a while, without blocking user interaction or other program execution. Perhaps the most common example of this is making a web request, waiting for its response, then doing something with said response. While you are waiting for the response, the user interface shouldn’t freeze.
This is where asynchronous operations come in. In essence, you tell the computer to “do X, and whenever that’s done, do Y. In the meantime, let me go about my business”. A real life analogy would be heating food in a microwave oven: You put your food in there, tell the oven what to do and to notify you when it’s done. While the oven is doing its thing, you can get on with setting the table. When it’s done, it gives you hot food, which you can then serve.
Callbacks: The Old, Ugly Way
If you are used to Java, you would often implement asynchronous operations by supplying a callback: a function that gets invoked once the operation is completed. You either instantiate and override a class that implements the required callback functions, or implement the interface directly in your class. The code to execute once the operation completes is nested at least two levels deep, or lives in some other method. Your options are to do some cumbersome nesting, or to artificially split up code that is part of the same action.
C# allows you to do both of those, but offers a much tidier approach. Out with the loads of extra classes and interfaces, in with async
, await
, and Task
.
C#;s async
Modifier and await
Keyword
In C#, if you want to do asynchronous work, start by adding the async modifier to the method where you’ll be doing it.
private async void MyMethod()
In this method, you can now use the await
keyword, for example, like the following:
var itemPrice = await api.getPrice(itemId);
The code in this method will execute up to this point, and not go any further until api.getPrice has finished its work and retrieved a value. Once in it has retrieved the item’s price from wherever, execution of our method continues at the next line, like regular program flow.
At this point you may object, since nothing but a few keywords distinguish the example from a normal synchronous program. The difference lies in the fact that other code is free to run while the awaited call is ongoing. The user interface will not become unresponsive.
This async method behaves exactly like a callback would. In contrast to a callback, all our related code gets to stay in one method, and remains easy to follow, much like a normal procedural program.
In this post I will explain how you can implement your own asynchronous methods for a few common use cases:
- Background operations where we don’t care about their completion; e.g. Favoriting an item in-app and syncing the favorite to a server (async void)
- Operations that need to complete before we can continue; e.g. time delays (async Task)
- Operations that take a while and yield a value; e.g. requesting information from a server (async Task
)
1. `async void` (Start and Forget)
The first case is the simplest one. Say you have an item in a mobile application that a user marks as a favorite or bookmark. You want to make sure the user can access these favorites on your website as well as in the mobile application. When they select “favorite” in the application, this can be saved locally with no delay. You want to send the “favorite” action to your server as well though, so it gets saved there. The latter part takes a while, but you don’t want it to lock up other things.
In your application, this operation is a background task that has no effect on what the user sees, so it doesn’t matter when it completes.
The following is what we would want our code to look like:
public void Favorite(int itemId) { // Store the favorite in a local list of favorites localFavorites.Add(itemId); // Synchronise with the server - this is the async operation api.AddFavorite(itemId); // Get an element in the user interface that represents the item var itemRow = GetItemRow(itemId); // Reflect in the interface that this item is favorited itemRow.FavoriteStar.Visible = true; }
As you may notice, none of the mentioned new keywords are used here. That’s because we don’t actually want to wait for anything. While 1api.AddFavorite1 takes a while, we will not wait for it in this method. The moment 1api.AddFavorite1 was called, we moved on to the next line to update the user interface.
Side note: I could have moved 1api.AddFavorite1 to be the last line of the method, but I’m trying to illustrate the point.
Now lets look at an implementation of api.AddFavorite
.
public class Api { private const string FavoriteUrl = "https://example.org/api/add_favorite/"; public async void AddFavorite(itemId) { var succeeded = false; // Keep retrying until the request succeeds while (!succeeded) { // Response will contain any info the server sends back, // plus info on whether the request succeeded var response = await httpClient.PostAsync( FavoriteUrl + itemId.ToString()); succeeded = response.IsSuccess; } } }
AddFavorite
will use httpClient
to send a request to the server with our data. If the request works out, the method completes, if not, it will try again. This is a simplified example; don’t endlessly retry requests like that, otherwise you’ll likely self-DDOS, but that’s another story.
Let’s dissect it. The async
modifier indicates that this method contains asynchronous operations (stuff that uses await
). The void
return type indicates it will not return anything, as usual. This is a special case though; the combination async void
means that this method will run asynchronously, but cannot be invoked with the await
keyword. These conditions let you invoke the method from a normal, non-async method, even though it does use async operations.
This way of using async is the equivalent of using a no-op (blank) callback function.
2. async Task
(Await Completion, Without Return Value)
The most straightforward use case of this one is a time delay. For an example, say you want to show the user a message, confirming that an item was added to their favorites. You want this little message to show up for 2 seconds, then to disappear by itself. Let’s add a method called TemporarilyShowMessage
to our class.
private async void TemporarilyShowMessage(string message, int duration) { // Create a new instance of Notice, our fictional message displaying // interface element var notice = new Notice(message); // Show the notice on the screen notice.Show(); // Wait a number of seconds await DelaySeconds(duration); // Remove the notice from the screen notice.Hide(); }
This example in itself is much like the api.AddFavorite
example earlier, in that it uses async void
. This means TemporarilyShowMessage
can be called once and it will do its thing without blocking other code. It’s a common occurrence.
The important line here is line 11; DelaySeconds
will take a number of seconds to complete, and it’s at that point that TemporarilyShowMessage
continues execution, because we are using the await
keyword. It reads as English, we await completion of DelaySeconds
, nothing more to it.
Now, lets look at how we could implement a DelaySeconds
method so that it is indeed await-able.
private async Task DelaySeconds(int duration) { await Task.Delay(TimeSpan.FromSeconds(duration)); }
Okay, I’ll admit that this is a pretty useless method, as I could’ve just used the one-liner. Bear with me though.
The important part that makes the method awaitable is its return type: Task
. Task is a bit of a weird return type in C#, in that you aren’t required to explicitly return anything from the method (as long as it’s async
); there is no return
statement in DelaySeconds
. The only difference with async void
is that async Task
allows you to await the method. You can only await Tasks.
A further note: As with any method that returns anything, you are not required to use its return value. In the case of Task
this means that you are not required to await
a method that has a return type of Task
. If you wish, you can use it just the same way you would use an async void
method. That would mean that the method is not waited for before the rest of the method executes, though.
3. async Task<T>
(Await Completion and Return Value)
In many cases, await
ed code needs a return value. You start an operation, and wait for it to produce a result, which you then do something with.
As an example, we’ll retrieve some data from a server. When a user clicks an article in the interface, we want to show the article’s text. We have an integer id representing an article, and we want to download the text of this article from the server. Here’s what that might look like:
private async void ArticleClicked(int articleId) { // Ask our api class to download and return the article text var articleText = await api.FetchArticleText(articleId); // Show the text in a container in our interface articleContainer.Text = articleText; }
Again, the method uses the async
operator to indicate it may do things asynchronously. We use the await
operator as before, but this time as part of an assignment. await api.FetchArticleText(articleId)
will yield a string.
Let’s see how api.fetchArticleText
might be implemented.
public class Api { private const string ArticleTextUrl = "https://example.com/api/article_text/"; public async Task<string> FetchArticleText(int articleId) { // Request the text from the API endpoint var response = await httpClient.GetAsync( ArticleTextUrl + articleId.ToString()); if (response.IsSuccess) { // When successfully retrieved, read the content we want from the // response object, and return it var stringResponse = await response.Content.ReadAsStringAsync(); return stringResponse; } // If we failed to retrieve the content return null; } }
FetchArticleText
has a return type of Task<string>
. This indicates two things: it’s await-able, and it will yield a string result. The method is marked as async
, because, again, it executes something in an asynchronous manner.
Like in the previous example, we’re not explicitly returning a Task
object here, but instead return a plain string
. This way, we are only using the Task
part of the return type to indicate it’s await
-able, but nothing more complex.
It’s worth noting that returning a plain value from a method with return type Task<T>
is only allowed when the method is marked as async
. Otherwise, you have to explicitly return an instance of Task<T>
(where T
is the type you want to return). More on that in part two.
Wrapping up
This has been a brief, hopefully accessible introduction to async
in C#. It should help you get started with this powerful syntax. I will follow this up with a second part, discussing the following, more advanced topics:
- Multi-method task processing (where you return a
Task
object that completes some time later, for more complicated tasks) - Cancelling ongoing tasks (using
CancellationTokenSource
) - Throwing and handling Task exceptions