Chapter 5. Writing asynchronous code

This chapter covers

  • What it means to write asynchronous code
  • Declaring asynchronous methods with the async modifier
  • Waiting asynchronously with the await operator
  • Language changes in async/await since C# 5
  • Following usage guidelines for asynchronous code

Asynchrony has been a thorn in the side of developers for years. It’s been known to be useful as a way of avoiding tying up a thread while waiting for some arbitrary task to complete, but it’s also been a pain in the neck to implement correctly.

Even within the .NET Framework (which is still relatively young in the grand scheme of things), we’ve had three models to try to make things simpler:

  • The BeginFoo/EndFoo approach from .NET 1.x, using IAsyncResult and AsyncCallback to propagate results
  • The event-based asynchronous pattern from .NET 2.0, as implemented by BackgroundWorker and WebClient
  • The Task Parallel Library (TPL) introduced in .NET 4.0 and expanded in .NET 4.5

Despite the TPL’s generally excellent design, writing robust and readable asynchronous code with it was hard. Although the support for parallelism was great, some aspects of general asynchrony are much better fixed in a language instead of purely in libraries.

The main feature of C# 5 is typically called async/await, and it builds on the TPL. It allows you to write synchronous-looking code that uses asynchrony where appropriate. Gone is the spaghetti of callbacks, event subscriptions, and fragmented error handling; instead, asynchronous code expresses its intentions clearly and in a form that builds on the structures that developers are already familiar with. The language construct introduced in C# 5 allows you to await an asynchronous operation. This awaiting looks very much like a normal blocking call in that the rest of your code won’t continue until the operation has completed, but it manages to do this without blocking the currently executing thread. Don’t worry if that statement sounds completely contradictory; all will become clear over the course of the chapter.

Async/await has evolved a little over time, and for simplicity I’ve included the new features from C# 6 and C# 7 alongside the original C# 5 descriptions. I’ve called out those changes so you know when you need a C# 6 or C# 7 compiler.

The .NET Framework embraced asynchrony wholeheartedly in version 4.5, exposing asynchronous versions of a great many operations following a task-based asynchronous pattern to give a consistent experience across multiple APIs. Similarly, the Windows Runtime platform, which is the basis of Universal Windows Applications (UWA/UWP), enforces asynchrony for all long-running (or potentially long-running) operations. Many other modern APIs rely heavily on asynchrony, such as Roslyn and HttpClient. In short, most C# developers will have to use asynchrony in at least some part of their work.

Note

The Windows Runtime platform is commonly known as WinRT; it’s not to be confused with Windows RT, which was an edition of Windows 8.x for ARM processors. Universal Windows Applications are an evolution of Windows Store applications. UWP is a further evolution of UWA from Windows 10 onward.

To be clear, C# hasn’t become omniscient, guessing where you might want to perform operations concurrently or asynchronously. The compiler is smart, but it doesn’t attempt to remove the inherent complexity of asynchronous execution. You still need to think carefully, but the beauty of async/await is that all the tedious and confusing boilerplate code that used to be required has gone. Without the distraction of all the fluff required to make your code asynchronous to start with, you can concentrate on the hard bits.

A word of warning: this topic is reasonably advanced. It has the unfortunate properties of being incredibly important (realistically, even entry-level developers need to have a reasonable understanding of it) but also quite tricky to get your head around to start with.

This chapter focuses on asynchrony from a “regular developer” perspective, so you can use async/await without needing to understand too much of the detail. Chapter 6 goes into a lot more of the complexity of the implementation. I feel you’ll be a better developer if you understand what’s going on behind the scenes, but you can certainly take what you’ll learn from this chapter and be productive with async/await before diving deeper. Even within this chapter, you’ll be looking at the feature in an iterative process, with more detail the further you go.

5.1. Introducing asynchronous functions

So far, I’ve claimed that C# 5 makes async easier, but I’ve given only a tiny description of the features involved. Let’s fix that and then look at an example.

C# 5 introduces the concept of an asynchronous function. This is always either a method or an anonymous function that’s declared with the async modifier, and it can use the await operator for await expressions.

Note

As a reminder, an anonymous function is either a lambda expression or an anonymous method.

The await expressions are the points where things get interesting from a language perspective: if the operation the expression is awaiting hasn’t completed yet, the asynchronous function will return immediately, and it’ll then continue where it left off (in an appropriate thread) when the value becomes available. The natural flow of not executing the next statement until this one has completed is still maintained but without blocking. I’ll break down that woolly description into more-concrete terms and behavior later, but you need to see an example before it’s likely to make any sense.

5.1.1. First encounters of the asynchronous kind

Let’s start with something simple that demonstrates asynchrony in a practical way. We often curse network latency for causing delays in our real applications, but latency does make it easy to show why asynchrony is so important—particularly when using a GUI framework such as Windows Forms. Our first example is a tiny Windows Forms app that fetches the text of this book’s homepage and displays the length of the HTML in a label.

Listing 5.1. Displaying a page length asynchronously
public class AsyncIntro : Form
{
    private static readonly HttpClient client = new HttpClient();
    private readonly Label label;
    private readonly Button button;
    public AsyncIntro()
    {
        label = new Label
        {
            Location = new Point(10, 20),
            Text = "Length"
        };
        button = new Button
        {
            Location = new Point(10, 50),
            Text = "Click"
        };
        button.Click += DisplayWebSiteLength;        1
        AutoSize = true;
        Controls.Add(label);
        Controls.Add(button);
    }

    async void DisplayWebSiteLength(object sender, EventArgs e)
    {
        label.Text = "Fetching...";
        string text = await client.GetStringAsync(   2
            "http://csharpindepth.com");             2
        label.Text = text.Length.ToString();         3
    }

    static void Main()
    {
        Application.Run(new AsyncIntro());           4
    }
}

  • 1 Wires up event handler
  • 2 Starts fetching the page
  • 3 Updates the UI
  • 4 Entry point; just runs the form

The first part of this code creates the UI and hooks up an event handler for the button in a straightforward way. It’s the DisplayWebSiteLength method that’s of interest here. When you click the button, the text of the homepage is fetched, and the label is updated to display the HTML length in characters.

Note

I’m not disposing of the task returned by GetStringAsync, even though Task implements IDisposable. Fortunately, you don’t need to dispose of tasks in general. The background of this is somewhat complicated, but Stephen Toub explains it in a blog post dedicated to the topic: http://mng.bz/E6L3.

I could’ve written a smaller example program as a console app, but hopefully listing 5.1 makes a more convincing demo. In particular, if you remove the async and await contextual keywords, change HttpClient to WebClient, and change GetStringAsync to DownloadString, the code will still compile and work, but the UI will freeze while it fetches the contents of the page. If you run the async version (ideally, over a slow network connection), you’ll see that the UI is responsive; you can still move the window around while the web page is fetching.

Note

HttpClient is in some senses the new and improved WebClient; it’s the preferred HTTP API for .NET 4.5 onward, and it contains only asynchronous operations.

Most developers are familiar with the two golden rules of threading in Windows Forms development:

  • Don’t perform any time-consuming action on the UI thread.
  • Don’t access any UI controls other than on the UI thread.

You may regard Windows Forms as a legacy technology these days, but most GUI frameworks have the same rules, and they’re easier to state than to obey. As an exercise, you might want to try a few ways of creating code similar to listing 5.1 without using async/await. For this extremely simple example, it’s not too bad to use the event-based WebClient.DownloadStringAsync method, but as soon as more complex flow control (error handling, waiting for multiple pages to complete, and so on) comes into the equation, the legacy code quickly becomes hard to maintain, whereas the C# 5 code can be modified in a natural way.

At this point, the DisplayWebSiteLength method feels somewhat magical: you know it does what you need it to, but you have no idea how. Let’s take it apart a little bit and save the gory details for later.

5.1.2. Breaking down the first example

You’ll start by slightly expanding the method. In listing 5.1, I used await directly on the return value of HttpClient.GetStringAsync, but you can separate the call from the awaiting part:

async void DisplayWebSiteLength(object sender, EventArgs e)
{
    label.Text = "Fetching...";
    Task<string> task = client.GetStringAsync("http://csharpindepth.com");
    string text = await task;
    label.Text = text.Length.ToString();
}

Notice that the type of task is Task<string>, but the type of the await task expression is simply string. In this sense, the await operator performs an unwrapping operation—at least when the value being awaited is a Task<TResult>. (As you’ll see, you can await other types, too, but Task<TResult> is a good starting point.) That’s one aspect of await that doesn’t seem directly related to asynchrony but makes life easier.

The main purpose of await is to avoid blocking while you wait for time-consuming operations to complete. You may be wondering how this all works in the concrete terms of threading. You’re setting label.Text at the start and end of the method, so it’s reasonable to assume that both of those statements are executed on the UI thread, and yet you’re clearly not blocking the UI thread while you wait for the web page to download.

The trick is that the method returns as soon as it hits the await expression. Until that point, it executes synchronously on the UI thread, like any other event handler would. If you put a breakpoint on the first line and hit it in the debugger, you’ll see that the stack trace shows that the button is busy raising its Click event, including the Button.OnClick method. When you reach the await, the code checks whether the result is already available, and if it’s not (which will almost certainly be the case), it schedules a continuation to be executed when the web operation has completed. In this example, the continuation executes the rest of the method, effectively jumping to the end of the await expression. The continuation is executed in the UI thread, which is what you need so that you can manipulate the UI.

Definition

A continuation is effectively a callback to be executed when an asynchronous operation (or any Task) has completed. In an async method, the continuation maintains the state of the method. Just as a closure maintains its environment in terms of variables, a continuation remembers the point where it reached, so it can continue from there when it’s executed. The Task class has a method specifically for attaching continuations: Task.ContinueWith.

If you then put a breakpoint in the code after the await expression and run the code again, then assuming that the await expression needed to schedule the continuation, you’ll see that the stack trace no longer has the Button.OnClick method in it. That method finished executing long ago. The call stack will now effectively be the bare Windows Forms event loop with a few layers of async infrastructure on top. The call stack will be similar to what you’d see if you called Control.Invoke from a background thread in order to update the UI appropriately, but it’s all been done for you. At first it can be unnerving to notice the call stack change dramatically under your feet, but it’s absolutely necessary for asynchrony to be effective.

The compiler achieves all of this by creating a complicated state machine. That’s an implementation detail you’ll look at in chapter 6, but for now you’re going to concentrate on the functionality that async/await provides. First, you need a more concrete description of what you’re trying to achieve and what the language specifies.

5.2. Thinking about asynchrony

If you ask a developer to describe asynchronous execution, chances are they’ll start talking about multithreading. Although that’s an important part of typical uses of asynchrony, it’s not required for asynchronous execution. To fully appreciate how the async feature of C# 5 works, it’s best to strip away any thoughts of threading and go back to basics.

5.2.1. Fundamentals of asynchronous execution

Asynchrony strikes at the very heart of the execution model that C# developers are familiar with. Consider simple code like this:

Console.WriteLine("First");
Console.WriteLine("Second");

You expect the first call to complete and then the second call to start. Execution flows from one statement to the next, in order. But an asynchronous execution model doesn’t work that way. Instead, it’s all about continuations. When you start doing something, you tell that operation what you want to happen when that operation has completed. You may have heard (or used) the term callback for the same idea, but that has a broader meaning than the one we’re after here. In the context of asynchrony, I’m using the term to refer to callbacks that preserve the state of the program rather than arbitrary callbacks for other purposes, such as GUI event handlers.

Continuations are naturally represented as delegates in .NET, and they’re typically actions that receive the results of the asynchronous operation. That’s why, to use the asynchronous methods in WebClient prior to C# 5, you’d wire up various events to say what code should be executed in the case of success, failure, and so on. The trouble is, creating all those delegates for a complicated sequence of steps ends up being very complicated, even with the benefit of lambda expressions. It’s even worse when you try to make sure that your error handling is correct. (On a good day, I can be reasonably confident that the success paths of handwritten asynchronous code are correct. I’m typically less certain that it reacts the right way on failure.)

Essentially, all that await in C# does is ask the compiler to build a continuation for you. For an idea that can be expressed so simply, however, the consequences for readability and developer serenity are remarkable.

My earlier description of asynchrony was an idealized one. The reality in the task-based asynchronous pattern is slightly different. Instead of the continuation being passed to the asynchronous operation, the asynchronous operation starts and returns a token you can use to provide the continuation later. It represents the ongoing operation, which may have completed before it has returned to the calling code or may still be in progress. That token is then used whenever you want to express this idea: I can’t proceed any further until this operation has completed. Typically, the token is in the form of a Task or Task<TResult>, but it doesn’t have to be.

Note

The token described here isn’t the same as a cancellation token, although both have the same emphasis on the fact that you don’t need to know what’s going on behind the scenes; you only need to know what the token allows you to do.

The execution flow in an asynchronous method in C# 5 typically follows these lines:

  1. Do some work.
  2. Start an asynchronous operation and remember the token it returns.
  3. Possibly do some more work. (Often, you can’t make any further progress until the asynchronous operation has completed, in which case this step is empty.)
  4. Wait for the asynchronous operation to complete (via the token).
  5. Do some more work.
  6. Finish.

If you didn’t care about exactly what the wait part meant, you could do all of this in C# 4. If you’re happy to block until the asynchronous operation completes, the token will normally provide you some way of doing so. For a Task, you could simply call Wait(). At that point, though, you’re taking up a valuable resource (a thread) and not doing any useful work. It’s a little like phoning for a delivery pizza and then standing at your front door until it arrives. What you really want to do is get on with something else and ignore the pizza until it arrives. That’s where await comes in.

When you wait for an asynchronous operation, you’re saying “I’ve gone as far as I can go for now. Keep going when the operation has completed.” But if you’re not going to block the thread, what can you do? Very simply, you can return right then and there. You’ll continue asynchronously yourself. And if you want your caller to know when your asynchronous method has completed, you’ll pass a token back to the caller, which they can block on if they want or (more likely) use with another continuation. Often, you’ll end up with a whole stack of asynchronous methods calling each other; it’s almost as if you go into an “async mode” for a section of code. Nothing in the language states that it has to be done that way, but the fact that the same code that consumes asynchronous operations also behaves as an asynchronous operation certainly encourages it.

5.2.2. Synchronization contexts

Earlier I mentioned that one of the golden rules of UI code is that you mustn’t update the user interface unless you’re on the right thread. In listing 5.1, which checked the length of a web page asynchronously, you needed to ensure that the code after the await expression executed on the UI thread. Asynchronous functions get back to the right thread by using SynchronizationContext, a class that’s existed since .NET 2.0 and is used by other components such as BackgroundWorker. A SynchronizationContext generalizes the idea of executing a delegate on an appropriate thread; its Post (asynchronous) and Send (synchronous) messages are similar to Control .BeginInvoke and Control.Invoke in Windows Forms.

Different execution environments use different contexts; for example, one context may let any thread from the thread pool execute the action it’s given. More contextual information exists than in the synchronization context, but if you start wondering how asynchronous methods manage to execute exactly where you want them to, it’s the synchronization context that you need to focus on.

For more information on SynchronizationContext, read Stephen Cleary’s MSDN magazine article on the topic (http://mng.bz/5cDw). In particular, pay careful attention if you’re an ASP.NET developer; the ASP.NET context can easily trap unwary developers into creating deadlocks within code that looks fine. The story changes slightly for ASP.NET Core, but Stephen has another blog post covering that: http://mng.bz/5YrO.

Use of Task.Wait() and Task.Result in examples

I’ve used Task.Wait() and Task.Result in some of the sample code because it leads to simple examples. It’s usually safe to do so in a console application because in that case there’s no synchronization context; continuations for async methods will always execute in the thread pool.

In real-world applications, you should take great care using these methods. They both block until they complete, which means if you call them from a thread that a continuation needs to execute on, you can easily deadlock your application.

With the theory out of the way, let’s take a closer look at the concrete details of asynchronous methods. Asynchronous anonymous functions fit into the same mental model, but it’s much easier to talk about asynchronous methods.

5.2.3. Modeling asynchronous methods

I find it useful to think about asynchronous methods as shown in figure 5.1.

Figure 5.1. Modeling asynchronous boundaries

Here you have three blocks of code (the methods) and two boundary types (the method return types). As a simple example, in a console-based version of our page-length fetching application, you might have code like the following.

Listing 5.2. Retrieving a page length in an asynchronous method
static readonly HttpClient client = new HttpClient();

static async Task<int> GetPageLengthAsync(string url)
{
    Task<string> fetchTextTask = client.GetStringAsync(url);
    int length = (await fetchTextTask).Length;
    return length;
}

static void PrintPageLength()
{
    Task<int> lengthTask =
        GetPageLengthAsync("http://csharpindepth.com");
    Console.WriteLine(lengthTask.Result);
}

Figure 5.2 shows how the concrete details in listing 5.2 map to the concepts in figure 5.1.

Figure 5.2. Applying the details of listing 5.2 to the general pattern shown in figure 5.1

You’re mainly interested in the GetPageLengthAsync method, but I’ve included PrintPageLength so you can see how the methods interact. In particular, you definitely need to know about the valid types at the method boundaries. I’ll repeat this diagram in various forms through the chapter.

You’re finally ready to look at writing async methods and the way they’ll behave. There’s a lot to cover here, as what you can do and what happens when you do it blend together to a large extent.

There are only two new pieces of syntax: async is a modifier used when declaring an asynchronous method, and the await operator is used to consume asynchronous operations. But following the way information is transferred between parts of your program gets complicated quickly, especially when you have to consider what happens when things go wrong. I’ve tried to separate out the different aspects, but your code will be dealing with everything at once. If you find yourself asking “But what about...?” while reading this section, keep reading; chances are your question will be answered soon.

The next three sections look at an asynchronous method in three stages:

  • Declaring the async method
  • Using the await operator to asynchronously wait for operations to complete
  • Returning a value when your method is complete

Figure 5.3 shows how these sections fit into our conceptual model.

Figure 5.3. Demonstrating how sections 5.3, 5.4, and 5.5 fit into the conceptual model of asynchrony

Let’s start with the method declaration itself; that’s the easiest bit.

5.3. Async method declarations

The syntax for an async method declaration is exactly the same as for any other method, except it has to include the async contextual keyword. This can appear anywhere before the return type. All of these are valid:

public static async Task<int> FooAsync() { ... }
public async static Task<int> FooAsync() { ... }
async public Task<int> FooAsync() { ... }
public async virtual Task<int> FooAsync() { ... }

My preference is to keep the async modifier immediately before the return type, but there’s no reason you shouldn’t come up with your own convention. As always, discuss it with your team and try to be consistent within one codebase.

Now, the async contextual keyword has a little secret: the language designers didn’t need to include it at all. In the same way the compiler goes into a sort of iterator block mode when you try to use yield return or yield break in a method with a suitable return type, the compiler could have spotted the use of await inside a method and used that to go into async mode. But I’m pleased that async is required, because it makes it much easier to read code written using asynchronous methods. It sets your expectations immediately, so you’re actively looking for await expressions, and you can actively look for any blocking calls that should be turned into an async call and an await expression.

The fact that the async modifier has no representation in the generated code is important, though. As far as the calling method is concerned, it’s a normal method that happens to return a task. You can change an existing method (with an appropriate signature) to use async, or you could go in the other direction; it’s a compatible change in terms of both source and binary. The fact that it’s a detail of the implementation of the method means that you can’t declare an abstract method or a method in an interface using async. It’s perfectly possible for there to be an interface specifying a method with a return type of Task<int>; one implementation of that interface can use async/await while another implementation uses a regular method.

5.3.1. Return types from async methods

Communication between the caller and the async method is effectively in terms of the value returned. In C# 5, asynchronous functions are limited to the following return types:

  • void
  • Task
  • Task<TResult> (for some type TResult, which could itself be a type parameter)

In C# 7, this list is expanded to include task types. You’ll come back to those in section 5.8 and then again in chapter 6.

The .NET 4 Task and Task<TResult> types both represent an operation that may not have completed yet; Task<TResult> derives from Task. The difference between the two is that Task<TResult> represents an operation that returns a value of type TResult, whereas Task need not produce a result at all. It’s still useful to return a Task, though, because it allows the calling code to attach its own continuations to the returned task, detect when the task has failed or completed, and so on. In some cases, you can think of Task as being like a Task<void> type, if such a thing were valid.

Note

F# developers can be justifiably smug about the Unit type at this point, which is similar to void but is a real type. The disparity between Task and Task<TResult> can be frustrating. If you could use void as a type argument, you wouldn’t need the Action family of delegates either; Action<string> is equivalent to Func<string, void>, for example.

The ability to return void from an async method is designed for compatibility with event handlers. For example, you might have a UI button click handler like this:

private async void LoadStockPrice(object sender, EventArgs e)
{
    string ticker = tickerInput.Text;
    decimal price = await stockPriceService.FetchPriceAsync(ticker);
    priceDisplay.Text = price.ToString("c");
}

This is an asynchronous method, but the calling code (the button OnClick method or whatever piece of framework code is raising the event) doesn’t care. It doesn’t need to know when you’ve finished handling the event—when you’ve loaded the stock price and updated the UI. It simply calls the event handler that it’s been given. The fact that the code generated by the compiler will end up with a state machine attaching a continuation to whatever is returned by FetchPriceAsync is an implementation detail.

You can subscribe to an event with the preceding method as if it were any other event handler:

loadStockPriceButton.Click += LoadStockPrice;

After all (and yes, I’m laboring this deliberately), it’s just a normal method as far as calling code is concerned. It has a void return type and parameters of type object and EventArgs, which makes it suitable as the action for an EventHandler delegate instance.

Warning

Event subscription is pretty much the only time I’d recommend returning void from an asynchronous method. Any other time you don’t need to return a specific value, it’s best to declare the method to return Task. That way, the caller is able to await the operation completing, detect failures, and so on.

Although the return type of async methods is fairly tightly restricted, most other aspects are as normal: async methods can be generic, static or nonstatic, and specify any of the regular access modifiers. Restrictions exist on the parameters you can use, however.

5.3.2. Parameters in async methods

None of the parameters in an async method can use the out or ref modifiers. This makes sense because those modifiers are for communicating information back to the calling code; some of the async method may not have run by the time control returns to the caller, so the value of the by-reference parameter might not have been set. Indeed, it could get stranger than that: imagine passing a local variable as an argument for a ref parameter; the async method could end up trying to set that variable after the calling method had already completed. It doesn’t make a lot of sense to try to do this, so the compiler prohibits it. Additionally, pointer types can’t be used as async method parameter types.

After you’ve declared the method, you can start writing the body and awaiting other asynchronous operations. Let’s look at how and where you can use await expressions.

5.4. Await expressions

The whole point of declaring a method with the async modifier is to use await expressions in that method. Everything else about the method looks pretty normal: you can use all kinds of control flow—loops, exceptions, using statements, anything. So where can you use an await expression, and what does it do?

The syntax for an await expression is simple: it’s the await operator followed by another expression that produces a value. You can await the result of a method call, a variable, a property. It doesn’t have to be a simple expression either. You can chain method calls together and await the result:

int result = await foo.Bar().Baz();

The precedence of the await operator is lower than that of the dot operator, so this code is equivalent to the following:

int result = await (foo.Bar().Baz());

Restrictions limit which expressions you can await, though. They have to be awaitable, and that’s where the awaitable pattern comes in.

5.4.1. The awaitable pattern

The awaitable pattern is used to determine types that can be used with the await operator. Figure 5.4 is a reminder that I’m talking about the second boundary from figure 5.1: how the async method interacts with another asynchronous operation. The awaitable pattern is a way of codifying what we mean by an asynchronous operation.

Figure 5.4. The awaitable pattern enables async methods to asynchronously wait for operations to complete

You might expect this to be expressed in terms of interfaces in the same way the compiler requires a type to implement IDisposable in order to support the using statement. Instead, it’s based on a pattern. Imagine that you have an expression of type T that you want to await. The compiler performs the following checks:

  • T must have a parameterless GetAwaiter() instance method, or there must be an extension method accepting a single parameter of type T. The GetAwaiter method has to be nonvoid. The return type of the method is called the awaiter type.
  • The awaiter type must implement the System.Runtime.INotifyCompletion interface. That interface has a single method: void OnCompleted (Action).
  • The awaiter type must have a readable instance property called IsCompleted of type bool.
  • The awaiter type must have a nongeneric parameterless instance method called GetResult.
  • The members listed previously don’t have to be public, but they need to be accessible from the async method you’re trying to await the value from. (Therefore, it’s possible that you can await a value of a particular type from some code but not in all code. That’s highly unusual, though.)

If T passes all of those checks, congratulations—you can await a value of type T! The compiler needs one more piece of information, though, to determine what the type of the await expression should be. That’s determined by the return type of the GetResult method of the awaiter type. It’s fine for it to be a void method, in which case the await expression is classified as an expression with no result, like an expression that calls a void method directly. Otherwise, the await expression is classified as producing a value of the same type as the return type of GetResult.

As an example, let’s consider the static Task.Yield() method. Unlike most other methods on Task, the Yield() method doesn’t return a task itself; it returns a YieldAwaitable. Here’s a simplified version of the types involved:

public class Task
{
    public static YieldAwaitable Yield();
}

public struct YieldAwaitable
{
    public YieldAwaiter GetAwaiter();

    public struct YieldAwaiter : INotifyCompletion
    {
        public bool IsCompleted { get; }
        public void OnCompleted(Action continuation);
        public void GetResult();
    }
}

As you can see, YieldAwaitable follows the awaitable pattern described previously. Therefore, this is valid:

public async Task ValidPrintYieldPrint()
{
    Console.WriteLine("Before yielding");
    await Task.Yield();                      1
    Console.WriteLine("After yielding");
}

  • 1 Valid

But the following is invalid, because it tries to use the result of awaiting a YieldAwaitable:

public async Task InvalidPrintYieldPrint()
{
    Console.WriteLine("Before yielding");
    var result = await Task.Yield();         1
    Console.WriteLine("After yielding");
}

  • 1 Invalid; this await expression doesn’t produce a value.

The middle line of InvalidPrintYieldPrint is invalid for exactly the same reason that it would be invalid to write this:

var result = Console.WriteLine("WriteLine is a void method");

No result is produced, so you can’t assign it to a variable.

Unsurprisingly, the awaiter type for Task has a GetResult method with a void return type, whereas the awaiter type for Task<TResult> has a GetResult method returning TResult.

Historical importance of extension methods

The fact that GetAwaiter can be an extension method is of more historical than contemporary importance. C# 5 was released in the same time frame as .NET 4.5, which introduced the GetAwaiter methods into Task and Task<TResult>. If GetAwaiter had to be a genuine instance method, that would’ve stranded developers who were tied to .NET 4.0. But with support for extension methods, Task and Task<TResult> could be async/await-enabled by using a NuGet package to provide those extension methods separately. This also meant that the community could test prereleases of the C# 5 compiler without testing prereleases of .NET 4.5.

In code targeting modern frameworks in which all the relevant GetAwaiter methods are already present, you’ll rarely need to use the ability to make an existing type awaitable via extension methods.

You’ll see more details about exactly how the members in the awaitable pattern are used in section 5.6, when you consider the execution flow of asynchronous methods. You’re not quite done with await expressions, though; a few restrictions exist.

5.4.2. Restrictions on await expressions

Like yield return, restrictions limit where you can use await expressions. The most obvious restriction is that you can use them only in async methods and async anonymous functions (which you’ll look at in section 5.7). Even within async methods, you can’t use the await operator within an anonymous function unless that’s async, too.

The await operator also isn’t allowed within an unsafe context. That doesn’t mean you can’t use unsafe code within an async method; you just can’t use the await operator within that part. The following listing shows a contrived example in which a pointer is used to iterate over the characters in a string to find the total of the UTF-16 code units in that string. It doesn’t do anything truly useful, but it demonstrates the use of an unsafe context within an async method.

Listing 5.3. Using unsafe code in an async method
static async Task DelayWithResultOfUnsafeCode(string text)
{
    int total = 0;
    unsafe                                               1
    {
        fixed (char* textPointer = text)
        {
            char* p = textPointer;
            while (*p != 0)
            {
                total += *p;
                p++;
            }
        }
    }
    Console.WriteLine("Delaying for " + total + "ms");
    await Task.Delay(total);                            2
    Console.WriteLine("Delay complete");
}

  • 1 It’s fine to have an unsafe context in an async method.
  • 2 But, the await expression can’t be inside it.

You also can’t use the await operator within a lock. If you ever find yourself wanting to hold a lock while an asynchronous operation completes, you should redesign your code. Don’t work around the compiler restriction by calling Monitor.TryEnter and Monitor.Exit manually with a try/finally block; change your code so you don’t need the lock during the operation. If this is really, really awkward in your situation, consider using SemaphoreSlim instead, with its WaitAsync method.

The monitor used by a lock statement can be released only by the same thread that originally acquired it, which goes against the distinct possibility that the thread executing the code before an await expression will be different from the one executing the code after it. Even if the same thread is used (for example, because you’re in a GUI synchronization context), some other code may well have executed on the same thread between the start and end of the asynchronous operation, and that other code would’ve been able to enter a lock statement for the same monitor, which almost certainly isn’t what you intended. Basically, lock statements and asynchrony don’t go well together.

There’s one final set of contexts in which the await operator was invalid in C# 5 but is valid from C# 6 onward:

  • Any try block with a catch block
  • Any catch block
  • Any finally block

It’s always been okay to use the await operator in a try block that has only a finally block, which means it’s always been okay to use await in a using statement. The C# design team didn’t figure out how to safely and reliably include await expressions in the contexts listed previously before C# 5 shipped. This was occasionally inconvenient, and the team worked out how to build the appropriate state machine while implementing C# 6, so the restriction is lifted there.

You now know how to declare an async method and how the await operator can be used within it. What about when you’ve completed your work? Let’s look at how values are returned back to the calling code.

5.5. Wrapping of return values

We’ve looked at how to declare the boundary between the calling code and the async method and how to wait for any asynchronous operations within the async method. Now let’s look at how return statements are used to implement that first boundary in terms of returning a value to the calling code; see figure 5.5.

Figure 5.5. Returning a result from an async method to its caller

You’ve already seen an example that returned data, but let’s look at it again, this time focusing on the return aspect alone. Here’s the relevant part of listing 5.2:

static async Task<int> GetPageLengthAsync(string url)
{
    Task<string> fetchTextTask = client.GetStringAsync(url);
    int length = (await fetchTextTask).Length;
    return length;
}

You can see that the type of length is int, but the return type of the method is Task<int>. The generated code takes care of the wrapping for you, so the caller gets a Task<int>, which will eventually have the value returned from the method when it completes. A method returning a nongeneric Task is like a normal void method: it doesn’t need a return statement at all, and any return statements it does have must be simply return rather than trying to specify a value. In either case, the task will also propagate any exception thrown within the async method. (You’ll look at exceptions in more detail in section 5.6.5.)

Hopefully, by now you should have a good intuition about why this wrapping is necessary; the method will almost certainly return to the caller before it hits the return statement, and it has to propagate the information to that caller somehow. A Task<TResult> (often known as a future in computer science) is the promise of a value—or an exception—at a later time.

As with normal execution flow, if the return statement occurs within the scope of a try block that has an associated finally block (including when all of this happens because of a using statement), the expression used to compute the return value is evaluated immediately, but it doesn’t become the result of the task until everything has been cleaned up. If the finally block throws an exception, you don’t get a task that both succeeds and fails; the whole thing will fail.

To reiterate a point I made earlier, it’s the combination of automatic wrapping and unwrapping that makes the async feature work so well with composition; async methods can consume the results of async methods easily, so you can build up complex systems from lots of small blocks. You can think of this as being a bit like LINQ: you write operations on each element of a sequence in LINQ, and the wrapping and unwrapping means you can apply those operations to sequences and get sequences back. In an async world, you rarely need to explicitly handle a task; instead, you await the task to consume it, and produce a result task automatically as part of the mechanism of the async method. Now that you know what an asynchronous method looks like, it’s easier to give examples to demonstrate the execution flow.

5.6. Asynchronous method flow

You can think about async/await at multiple levels:

  • You can simply expect that awaiting will do what you want without defining exactly what that means.
  • You can reason about how the code will execute, in terms of what happens when and in which thread, but without understanding how that’s achieved.
  • You can dig deeply into the infrastructure that makes all of this happen.

So far, we’ve mostly been thinking at the first level, dipping down to the second occasionally. This section focuses on the second level, effectively looking at what the language promises. We’ll leave the third bullet to the next chapter, where you’ll see what the compiler is doing under the covers. (Even then, you could always go further; this book doesn’t talk about anything below the IL level. We don’t get into the operating system or hardware support for asynchrony and threading.)

For the vast majority of the time when you’re developing, it’s fine to switch between the first two levels, depending on your context. Unless I’m writing code that’s coordinating multiple operations, I rarely need to even think at the second level of detail. Most of the time, I’m happy to just let things work. What’s important is that you can think about the details when you need to.

5.6.1. What is awaited and when?

Let’s start by simplifying things a bit. Sometimes await is used with the result of a chained method call or occasionally a property, like this:

string pageText = await new HttpClient().GetStringAsync(url);

This makes it look as if await can modify the meaning of the whole expression. In reality, await always operates on only a single value. The preceding line is equivalent to this:

Task<string> task = new HttpClient().GetStringAsync(url);
string pageText = await task;

Similarly, the result of an await expression can be used as a method argument or within another expression. Again, it helps if you can mentally separate out the await-specific part from everything else.

Imagine you have two methods, GetHourlyRateAsync() and GetHoursWorkedAsync(), returning a Task<decimal> and a Task<int>, respectively. You might have this complicated statement:

AddPayment(await employee.GetHourlyRateAsync() *
           await timeSheet.GetHoursWorkedAsync(employee.Id));

The normal rules of C# expression evaluation apply, and the left operand of the * operator has to be completely evaluated before the right operand is evaluated, so the preceding statement can be expanded as follows:

Task<decimal> hourlyRateTask = employee.GetHourlyRateAsync();
decimal hourlyRate = await hourlyRateTask;
Task<int> hoursWorkedTask = timeSheet.GetHoursWorkedAsync(employee.Id);
int hoursWorked = await hoursWorkedTask;
AddPayment(hourlyRate * hoursWorked);

How you write the code is a different matter. If you find the single statement version easier to read, that’s fine; if you want to expand it all out, you’ll end up with more code, but it may be simpler to understand and debug. You could decide to use a third form that looks similar but isn’t quite the same:

Task<decimal> hourlyRateTask = employee.GetHourlyRateAsync();
Task<int> hoursWorkedTask = timeSheet.GetHoursWorkedAsync(employee.Id);
AddPayment(await hourlyRateTask * await hoursWorkedTask);

I find that this is the most readable form, and it has potential performance benefits, too. You’ll come back to this example in section 5.10.2.

The key takeaway from this section is that you need to be able to work out what’s being awaited and when. In this case, the tasks returned from GetHourlyRateAsync and GetHoursWorkedAsync are being awaited. In every case, they’re being awaited before the call to AddPayment is executed, which makes sense, because you need the intermediate results so you can multiply them together and pass the result of that multiplication as an argument. If this were using synchronous calls, all of this would be obvious; my aim is to demystify the awaiting part. Now that you know how to simplify complex code into the value you’re awaiting and when you’re awaiting it, you can move on to what happens when you’re in the awaiting part itself.

5.6.2. Evaluation of await expressions

When execution reaches the await expression, you have two possibilities: either the asynchronous operation you’re awaiting has already completed or it hasn’t. If the operation has already completed, the execution flow is simple: it keeps going. If the operation failed and it captured an exception to represent that failure, the exception is thrown. Otherwise, any result from the operation is obtained (for example, extracting the string from a Task<string>) and you move on to the next part of the program. All of this is done without any thread context switching or attaching continuations to anything.

In the more interesting scenario, the asynchronous operation is still ongoing. In this case, the method waits asynchronously for the operation to complete and then continues in an appropriate context. This asynchronous waiting really means the method isn’t executing at all. A continuation is attached to the asynchronous operation, and the method returns. The async infrastructure makes sure that the continuation executes on the right thread: typically, either a thread-pool thread (where it doesn’t matter which thread is used) or the UI thread where that makes sense. This depends on the synchronization context (discussed in section 5.2.2) and can also be controlled using Task.ConfigureAwait, which we’ll talk about in section 5.10.1.

Returning vs. completing

Possibly the hardest part of describing asynchronous behavior is talking about when the method returns (either to the original caller or to whatever called a continuation) and when the method completes. Unlike most methods, an asynchronous method can return multiple times—effectively, when it has no more work it can do for the moment.

To return to our earlier pizza delivery analogy, if you have an EatPizzaAsync method that involves calling the pizza company to place an order, meeting the delivery person, waiting for the pizza to cool down a bit, and then finally eating it, the method might return after each of the first three parts, but it won’t complete until the pizza is eaten.

From the developer’s point of view, this feels like the method is paused while the asynchronous operation completes. The compiler makes sure that all the local variables used within the method have the same values as they did before the continuation, as it does with iterator blocks.

Let’s look at an example of the two cases with a small console application that uses a single asynchronous method awaiting two tasks. Task.FromResult always returns a completed task, whereas Task.Delay returns a task that completes after the specified delay.

Listing 5.4. Awaiting completed and noncompleted tasks
static void Main()
{
    Task task = DemoCompletedAsync();         1
    Console.WriteLine("Method returned");
    task.Wait();                              2
    Console.WriteLine("Task completed");
}

static async Task DemoCompletedAsync()
{
    Console.WriteLine("Before first await");
    await Task.FromResult(10);                3
    Console.WriteLine("Between awaits");
    await Task.Delay(1000);                   4
    Console.WriteLine("After second await");
}

  • 1 Calls the async method
  • 2 Blocks until the task completes
  • 3 Awaits a completed task
  • 4 Awaits a noncompleted task

The output from listing 5.4 is as follows:

Before first await
Between awaits
Method returned
After second await
Task completed

The important aspects of the ordering are as follows:

  • The async method doesn’t return when awaiting the completed task; the method keeps executing synchronously. That’s why you see the first two lines with nothing between.
  • The async method does return when awaiting the delay task. That’s why the third line is Method returned, printed in the Main method. The async method can tell that the operation it’s waiting for (the delay task) hasn’t completed yet, so it returns to avoid blocking.
  • The task returned from the async method completes only when the method completes. That’s why Task completed is printed after After second await.

I’ve attempted to capture the await expression flow in figure 5.6, although classic flowcharts weren’t really designed with asynchronous behavior in mind.

Figure 5.6. User-visible model of await handling

You could think of the dotted line as being another line coming into the top of the flowchart as an alternative. Note that I’m assuming the target of the await expression has a result. If you’re awaiting a plain Task or something similar, fetch result really means check that the operation completed successfully.

It’s worth stopping to think briefly about what it means to return from an asynchronous method. Again, two possibilities exist:

  • This is the first await expression you’ve had to wait for, so you still have the original caller somewhere in your stack. (Remember that until you really need to wait, the method executes synchronously.)
  • You’ve already awaited something else that hadn’t already completed, so you’re in a continuation that has been called by something. Your call stack will almost certainly have changed significantly from the one you’d have seen when you first entered the method.

In the first case, you’ll usually end up returning a Task or Task<TResult> to the caller. Obviously, you don’t have the result of the method yet; even if there’s no value to return as such, you don’t know whether the method will complete without exceptions. Because of this, the task you’ll be returning has to be a noncompleted one.

In the latter case, the something calling you back depends on your context. For example, in a Windows Forms UI, if you started your async method on the UI thread and didn’t deliberately switch away from it, the whole method would execute on the UI thread. For the first part of the method, you’ll be in some event handler or other—whatever kicked off the async method. Later, however, you’d be called back by the Windows Forms internal machinery (usually known as the message pump) pretty directly, as if you were using Control.BeginInvoke(continuation). Here, the calling code—whether it’s the Windows Forms message pump, part of the thread-pool machinery, or something else—doesn’t care about your task.

As a reminder, until you hit the first truly asynchronous await expression, the method executes entirely synchronously. Calling an asynchronous method isn’t like firing up a new task in a separate thread, and it’s up to you to make sure that you always write async methods so they return quickly. Admittedly, it depends on the context in which you’re writing code, but you should generally avoid performing long-running blocking work in an async method. Separate it out into another method that you can create a Task for.

I’d like to briefly revisit the case where the value you’re awaiting is already complete. You might be wondering why an operation that completes immediately would be represented with asynchrony in the first place. It’s a little bit like calling the Count() method on a sequence in LINQ: in the general case, you may need to iterate over every item in the sequence, but in some situations (such as when the sequence turns out to be a List<T>), an easy optimization is available. It’s useful to have a single abstraction that covers both scenarios, but without paying an execution-time price.

As a real-world example in the asynchronous API case, consider reading asynchronously from a stream associated with a file on disk. All the data you want to read may already have been fetched from disk into memory, perhaps as part of previous ReadAsync call request, so it makes sense to use it immediately without going through all the other async machinery. As another example, you may have a cache within your architecture; that can be transparent if you have an asynchronous operation that fetches a value either from the in-memory cache (returning a completed task) or hits storage (returning a noncompleted task that’ll complete when the storage call completes). Now that you know the basics of the flow, you can see where the awaitable pattern fits into the jigsaw.

5.6.3. The use of awaitable pattern members

In section 5.4.1, I described the awaitable pattern that a type has to implement in order for you to be able to await an expression of that type. You can now map the different bits of the pattern onto the behavior you’re trying to achieve. Figure 5.7 is the same as figure 5.6 but expanded a little and reworded to use the awaitable pattern instead of general descriptions.

Figure 5.7. Await handling via the awaitable pattern

When it’s written like this, you might be wondering what all the fuss is about; why is it worth having language support at all? Attaching a continuation is more complex than you might imagine, though. In simple cases, when the control flow is entirely linear (do some work, await something, do some more work, await something else), it’s pretty easy to imagine what the continuation might look like as a lambda expression, even if it wouldn’t be pleasant. As soon as the code contains loops or conditions, however, and you want to keep the code within one method, life becomes much more complicated. It’s here that the benefits of async/await really kick in. Although you could argue that the compiler is merely applying syntactic sugar, there’s an enormous difference in readability between manually creating the continuations and getting the compiler to do so for you.

So far, I’ve described the happy path where all the values we await complete successfully. What happens on failure?

5.6.4. Exception unwrapping

The idiomatic way of representing failures in .NET is via exceptions. Like returning a value to the caller, exception handling requires extra support from the language. When you await an asynchronous operation that’s failed, it may have failed a long time ago on a completely different thread. The regular synchronous way of propagating exceptions up the stack doesn’t occur naturally. Instead, the async/await infrastructure takes steps to make the experience of handling asynchronous failures as similar as possible to synchronous failures. If you think of failure as another kind of result, it makes sense that exceptions and return values are handled similarly. You’ll look at how exceptions are propagated out of an asynchronous method in section 5.6.5, but before that, you’ll see what happens when you await a failed operation.

In the same way that the GetResult() method of an awaiter is meant to fetch the return value if there is one, it’s also responsible for propagating any exceptions from the asynchronous operation back to the method. This isn’t quite as simple as it sounds, because in an asynchronous world, a single Task can represent multiple operations, leading to multiple failures. Although other awaitable pattern implementations are available, it’s worth considering Task and Task<TResult> specifically, as they’re the types you’re likely to be awaiting for the vast majority of the time.

Task and Task<TResult> indicate failures in multiple ways:

  • The Status of a task becomes Faulted when the asynchronous operation has failed (and IsFaulted returns true).
  • The Exception property returns an AggregateException that contains all the (potentially multiple) exceptions that caused the task to fail or null if the task isn’t faulted.
  • The Wait() method throws an AggregateException if the task ends up in a faulted state.
  • The Result property of Task<TResult> (which also waits for completion) likewise throws an AggregateException.

Additionally, tasks support the idea of cancellation via CancellationTokenSource and CancellationToken. If a task is canceled, the Wait() method and Result properties will throw an AggregateException containing an OperationCanceledException (in practice, a TaskCanceledException that derives from OperationCanceledException), but the status becomes Canceled instead of Faulted.

When you await a task, if it’s either faulted or canceled, an exception will be thrown but not the AggregateException. Instead, for convenience (in most cases), the first exception within the AggregateException is thrown. In most cases, this is what you want. It’s in the spirit of the async feature to allow you to write asynchronous code that looks much like the synchronous code you’d otherwise write. For example, consider the following listing, which tries to fetch one URL at a time until either one of them succeeds or you run out of URLs to try.

Listing 5.5. Catching exceptions when fetching web pages
async Task<string> FetchFirstSuccessfulAsync(IEnumerable<string> urls)
{
    var client = new HttpClient();
    foreach (string url in urls)
    {
        try
        {
            return await client.GetStringAsync(url);    1
        }
        catch (HttpRequestException exception)          2
        {
            Console.WriteLine("Failed to fetch {0}: {1}",
                url, exception.Message);
        }
    }
    throw new HttpRequestException("No URLs succeeded");
}

  • 1 Returns the string if successful
  • 2 Catches and displays the failure otherwise

For the moment, ignore the fact that you’re losing all the original exceptions and that you’re fetching all the pages sequentially. The point I’m trying to make is that catching HttpRequestException is what you’d expect here; you’re trying an asynchronous operation with an HttpClient, and if something fails, it’ll throw an HttpRequestException. You want to catch and handle that, right? That certainly feels like what you’d want to do—but the GetStringAsync() call can’t throw an HttpRequestException for an error such as the server timing out because the method only starts the operation. By the time it spots that error, the method has returned. All it can do is return a task that ends up being faulted and containing an HttpRequestException. If you simply called Wait() on the task, an AggregateException would be thrown that contains the HttpRequestException within it. The task awaiter’s GetResult method throws the HttpRequestException instead, and it’s caught by the catch block as normal.

Of course, this can lose information. If there are multiple exceptions in a faulted task, GetResult can throw only one of them, and it arbitrarily uses the first. You might want to rewrite the preceding code so that on failure, the caller can catch an AggregateException and examine all the causes of the failure. Importantly, some framework methods do this. For example, Task.WhenAll() is a method that’ll asynchronously wait for multiple tasks (specified in the method call) to complete. If any of them fails, the result is a failure that’ll contain the exceptions from all the faulted tasks. But if you await only the task returned by WhenAll(), you’ll see only the first exception. Typically, if you want to check the exceptions in detail, the simplest approach is to use Task.Exception for each of the original tasks.

To conclude, you know that the awaiter type’s GetResult() method is used to propagate both successful results and exceptions when awaiting. In the case of Task and Task<TResult>, GetResult() unwraps a failed task’s AggregateException to throw the first of its inner exceptions. That explains how an async method consumes another asynchronous operation—but how does it propagate its own result to calling code?

5.6.5. Method completion

Let’s recap a few points:

  • An async method usually returns before it completes.
  • It returns as soon as it hits an await expression where the operation that’s being awaited hasn’t already finished.
  • Assuming it’s not a void method (in which case the caller has no easy way of telling what’s going on), the value the method returns will be a task of some kind: Task or Task<TResult> before C# 7, with the option of a custom task type (which is explained in section 5.8) in C# 7 and onward. For the moment, let’s assume it’s a Task<TResult> for simplicity.
  • That task is responsible for indicating when and how the async method completes. If the method completes normally, the task status changes to RanToCompletion and the Result property holds the return value. If the method body throws an exception, the task status changes to Faulted (or Canceled depending on the exception) and the exception is wrapped into an AggregateException for the task’s Exception property.
  • When the task status changes to any of these terminal states, any continuations associated with it (such as code in any asynchronous method awaiting the task) can be scheduled to run.
Yes, this sounds like it’s repetition

You may be wondering whether you’ve accidentally skipped back a couple of pages and read them twice. Didn’t you just look at the same ideas when you awaited something?

Absolutely. All I’m doing is showing what the async method does to indicate how it completes rather than how an await expression examines how something else has completed. If these didn’t feel the same, that would be odd, because usually async methods are chained together: the value you’re awaiting in one async method is probably the value returned by another async method. In fancier terms, async operations compose easily.

All of this is done for you by the compiler with the help of a fair amount of infrastructure. You’ll look at some of those details in the next chapter (although not every single nook and cranny; even I have limits). This chapter is more about the behavior you can rely on in your code.

Returning successfully

The success case is the simplest one: if the method is declared to return a Task <TResult>, the return statement has to provide a value of type T (or something that can be converted to TResult), and the async infrastructure propagates that to the task.

If the return type is Task or void, any return statements have to be of the form return without a value, or it’s fine to let execution reach the end of the method, like a nonasync void method. In both cases, there’s no value to propagate, but the status of the task changes appropriately.

Lazy exceptions and argument validation

The most important point to note about exceptions is that an async method never directly throws an exception. Even if the first thing the method body does is throw an exception, it’ll return a faulted task. (The task will be immediately faulted in this case.) This is a bit of a pain in terms of argument validation. Suppose you want to do some work in an async method after validating that the parameters don’t have null values. If you validate the parameters as you would in a normal synchronous code, the caller won’t have any indication of the problem until the task is awaited. The following listing gives an example.

Listing 5.6. Broken argument validation in an async method
static async Task MainAsync()
{
    Task<int> task = ComputeLengthAsync(null);              1
    Console.WriteLine("Fetched the task");
    int length = await task;                                2
    Console.WriteLine("Length: {0}", length);
}

static async Task<int> ComputeLengthAsync(string text)
{
    if (text == null)
    {
        throw new ArgumentNullException("text");            3
    }
    await Task.Delay(500);                                  4
    return text.Length;
}

  • 1 Deliberately passes a bad argument
  • 2 Awaits the result
  • 3 Throws an exception as early as possible
  • 4 Simulates real asynchronous work

The output shows Fetched the task before it fails. The exception has been thrown synchronously before that output is written, because there are no await expressions before the validation, but the calling code won’t see it until it awaits the returned task. Some argument validation can sensibly be done up front without taking a long time (or incurring other asynchronous operations). In these cases, it’d be better if the failure were reported immediately, before the system can get itself into further trouble. As an example, HttpClient.GetStringAsync will throw an exception immediately if you pass it a null reference.

Note

If you’ve ever written an iterator method that needs to validate its arguments, this may sound familiar. It’s not quite the same, but it has a similar effect. In iterator blocks, any code in the method, including argument validation, doesn’t execute at all until the first call to MoveNext() on the sequence returned by the method. In the asynchronous case, the argument validation occurs immediately, but the exception won’t be obvious until you await the result.

You may not be too worried about this. Eager argument validation may be regarded as a nice-to-have feature in many cases. I’ve certainly become a lot less pedantic about this in my own code, as a matter of pragmatism; in most cases, the difference in timing isn’t terribly important. But if you do want to throw an exception synchronously from a method returning a task, you have three options, all of which are variations on the same theme.

The idea is to write a nonasync method that returns a task and is implemented by validating the arguments and then calling a separate async function that assumes the argument has already been validated. The three variations are in terms of how the async function is represented:

  • You can use a separate async method.
  • You can use an async anonymous function (which you’ll see in the next section).
  • In C# 7 and above, you can use a local async method.

My preference is the last of these; it has the benefit of not introducing another method into the class without the downside of having to create a delegate. Listing 5.7 shows the first option, as that doesn’t rely on anything we haven’t already covered, but the code for the other options is similar (and is in the downloadable code for book). This is only the ComputeLengthAsync method; the calling code doesn’t need to change.

Listing 5.7. Eager argument validation with a separate method
static Task<int> ComputeLengthAsync(string text)             1
{
    if (text == null)
    {
        throw new ArgumentNullException("text");
    }
    return ComputeLengthAsyncImpl(text);                     2
}

static async Task<int> ComputeLengthAsyncImpl(string text)
{
    await Task.Delay(500);                                   3
    return text.Length;
}

  • 1 Nonasync method so exceptions aren’t wrapped in a task
  • 2 After validation, delegate to implementation method.
  • 3 Implementation async method assumes validated input

Now when ComputeLengthAsync is called with a null argument, the exception is thrown synchronously rather than returning a faulted task.

Before moving on to asynchronous anonymous functions, let’s briefly revisit cancellation. I’ve mentioned this a couple of times in passing, but it’s worth considering in a bit more detail.

Handling cancellation

The Task Parallel Library (TPL) introduced a uniform cancellation model into .NET 4 using two types: CancellationTokenSource and CancellationToken. The idea is that you can create a CancellationTokenSource and then ask it for a CancellationToken, which is passed to an asynchronous operation. You can perform the cancellation on only the source, but that’s reflected to the token. (Therefore, you can pass out the same token to multiple operations and not worry about them interfering with each other.) There are various ways of using the cancellation token, but the most idiomatic approach is to call ThrowIfCancellationRequested, which will throw OperationCanceledException if the token has been canceled and will do nothing otherwise.[1] The same exception is thrown by synchronous calls (such as Task.Wait) if they’re canceled.

1

An example of this is available in the downloadable source code.

How this interacts with asynchronous methods is undocumented in the C# specification. According to the specification, if an asynchronous method body throws any exception, the task returned by the method will be in a faulted state. The exact meaning of faulted is implementation specific, but in reality, if an asynchronous method throws an OperationCanceledException (or a derived exception type, such as TaskCanceledException), the returned task will end up with a status of Canceled. You can demonstrate that it’s only the type of exception that determines the status by throwing an OperationCanceledException directly without the use of any cancellation tokens.

Listing 5.8. Creating a canceled task by throwing OperationCanceledException
static async Task ThrowCancellationException()
{
    throw new OperationCanceledException();
}
...
Task task = ThrowCancellationException();
Console.WriteLine(task.Status);

This outputs Canceled rather than the Faulted you might expect from the specification. If you Wait() on the task or ask for its result (in the case of a Task<TResult>), the exception is still thrown within an AggregateException, so it’s not like you need to explicitly start checking for cancellation on every task you use.

Off to the races?

You might be wondering if there’s a race condition in listing 5.8. After all, you’re calling an asynchronous method and then immediately expecting the status to be fixed. If this code were starting a new thread, that would be dangerous—but it’s not.

Remember that before the first await expression, an asynchronous method runs synchronously. It still performs result and exception wrapping, but the fact that it’s in an asynchronous method doesn’t necessarily mean there are any more threads involved. The ThrowCancellationException method doesn’t contain any await expressions, so the whole method runs synchronously; you know that we’ll have a result by the time it returns. Visual Studio issues a warning for any asynchronous function that doesn’t contain any await expressions, but in this case it’s exactly what you want.

Importantly, if you await an operation that’s canceled, the original OperationCanceledException is thrown. Consequently, unless you take any direct action, the task returned from the asynchronous method will also be canceled; cancellation is propagated in a natural fashion.

Congratulations on making it this far. You’ve now covered most of the hard parts for this chapter. You still have a couple of features to learn about, but they’re much easier to understand than the preceding sections. It’ll get tough again in the next chapter when we dissect what the compiler’s doing behind the scenes, but for now you can enjoy relative simplicity.

5.7. Asynchronous anonymous functions

I won’t spend much time on asynchronous anonymous functions. As you’d probably expect, they’re a combination of two features: anonymous functions (lambda expressions and anonymous methods) and asynchronous functions (code that can include await expressions). They allow you to create delegates that represent asynchronous operations. Everything you’ve learned so far about asynchronous methods applies to asynchronous anonymous functions, too.

Note

In case you were wondering, you can’t use asynchronous anonymous functions to create expression trees.

You create an asynchronous anonymous function like any other anonymous method or lambda expression by simply adding the async modifier at the start. Here’s an example:

Func<Task> lambda = async () => await Task.Delay(1000);
Func<Task<int>> anonMethod = async delegate()
{
    Console.WriteLine("Started");
    await Task.Delay(1000);
    Console.WriteLine("Finished");
    return 10;
};

The delegate you create has to have a signature with a return type that would be suitable for an asynchronous method (void, Task, or Task<TResult> for C# 5 and 6, with the option of a custom task type in C# 7). You can capture variables, as with other anonymous functions, and add parameters. Also, the asynchronous operation doesn’t start until the delegate is invoked, and multiple invocations create multiple operations. Delegate invocation does start the operation, though; as with a call to an async method, it’s not awaiting the task that starts an operation, and you don’t have to use await with the result of an asynchronous anonymous function at all. The following listing shows a slightly fuller (although still pointless) example.

Listing 5.9. Creating and calling an asynchronous function using a lambda expression
Func<int, Task<int>> function = async x =>
{
    Console.WriteLine("Starting... x={0}", x);
    await Task.Delay(x * 1000);
    Console.WriteLine("Finished... x={0}", x);
    return x * 2;
};
Task<int> first = function(5);
Task<int> second = function(3);
Console.WriteLine("First result: {0}", first.Result);
Console.WriteLine("Second result: {0}", second.Result);

I’ve deliberately chosen the values here so that the second operation completes quicker than the first. But because you’re waiting for the first to finish before printing the results (using the Result property, which blocks until the task has completed—again, be careful where you run this), the output looks like this:

Starting... x=5
Starting... x=3
Finished... x=3
Finished... x=5
First result: 10
Second result: 6

All of this behaves exactly the same as if you’d put the asynchronous code into an asynchronous method.

I’ve written far more async methods than async anonymous functions, but they can be useful, particularly with LINQ. You can’t use them in LINQ query expressions, but calling the equivalent methods directly works. It has limitations, though: because an async function can never return bool, you can’t call Where with an async function, for example. I’ve most commonly used Select to transform a sequence of tasks of one type to a sequence of tasks of a different type. Now I’ll address a feature I’ve referred to a few times already: an extra level of generalization introduced by C# 7.

5.8. Custom task types in C# 7

In C# 5 and C# 6, asynchronous functions (that is, async methods and async anonymous functions) could return only void, Task, or Task<TResult>. C# 7 loosens this restriction slightly and allows any type that’s decorated in a particular way to be used as a return type for asynchronous functions.

As a reminder, the async/await feature has always allowed us to await custom types that follow the awaitable pattern. The new feature here permits writing an async method that returns a custom type.

This is simultaneously complex and simple. It’s complex in that if you want to create your own task type, you have some fiddly work ahead of you. It’s not for the fainthearted. It’s simple in that you’re almost certainly not going to want to do this other than for experimentation; you’re going to want to use ValueTask<TResult>. Let’s look at that now.

5.8.1. The 99.9% case: ValueTask<TResult>

At the time of this writing, the System.Threading.ValueTask<TResult> type is present out of the box only in the netcoreapp2.0 framework, but it’s also available in the System.Threading.Tasks.Extensions package from NuGet, which makes it far more widely applicable. (Most important, that package includes a target for netstandard1.0.)

ValueTask<TResult> is simple to describe: it’s like Task<TResult>, but it’s a value type. It has an AsTask method that allows you to obtain a regular task from it when you want (for example, to include as one element in a Task.WhenAll or Task.WhenAny call), but most of the time, you’ll want to await it as you would a task.

What’s the benefit of ValueTask<TResult> over Task<TResult>? It all comes down to heap allocation and garbage collection. Task<TResult> is a class, and although the async infrastructure reuses completed Task<TResult> objects in some cases, most async methods will need to create a new Task<TResult>. Allocating objects in .NET is cheap enough that in many cases you don’t need to worry about it, but if you’re doing it a lot or if you’re working under tight performance constraints, you want to avoid that allocation if possible.

If an async method uses an await expression on something that’s incomplete, object allocation is unavoidable. It’ll return immediately, but it has to schedule a continuation to execute the rest of the method when the awaited operation has completed. In most async methods, this is the common case; you don’t expect the operation you’re awaiting to have completed before you await it. In those cases, ValueTask<TResult> provides no benefit and can even be a little more expensive.

In a few cases, though, the already completed case is the most common one, and that’s where ValueTask<TResult> is useful. To demonstrate this, let’s consider a simplified version of a real-world example. Suppose you want to read a byte at a time from a System.IO.Stream and do so asynchronously. You can easily add a buffering abstraction layer to avoid calling ReadAsync on the underlying Stream too often, but you’d then want to add an async method to encapsulate the operation of populate the buffer from the stream where necessary, then return the next byte. You can use byte? with a null value to indicate that you’ve reached the end of the data. That method is easy to write, but if every call to it allocates a new Task<byte?>, you’ll be hammering the garbage collector pretty hard. With ValueTask<TResult>, heap allocation is required only in the rare cases when you need to refill the buffer from the stream. The following listing shows the wrapper type (ByteStream) and an example of using it.

Listing 5.10. Wrapping a stream for efficient asynchronous byte-wise access
public sealed class ByteStream : IDisposable
{
    private readonly Stream stream;
    private readonly byte[] buffer;
    private int position;                                    1
    private int bufferedBytes;                               2

    public ByteStream(Stream stream)
    {
        this.stream = stream;
        buffer = new byte[1024 * 8];                         3
    }

    public async ValueTask<byte?> ReadByteAsync()
    {
        if (position == bufferedBytes)                       4
        {
            position = 0;
            bufferedBytes = await
                stream.ReadAsync(buffer, 0, buffer.Length)   5
                       .ConfigureAwait(false);               6
            if (bufferedBytes == 0)
            {
                return null;                                 7
            }
        }
        return buffer[position++];                           8
    }

    public void Dispose()
    {
        stream.Dispose();
    }
}

Sample usage
using (var stream = new ByteStream(File.OpenRead("file.dat")))
{
    while ((nextByte = await stream.ReadByteAsync()).HasValue)
    {
        ConsumeByte(nextByte.Value);                         9
    }
}

  • 1 Next buffer index to return
  • 2 Number of read bytes in the buffer
  • 3 An 8 KB buffer will mean you rarely need to await.
  • 4 Refills the buffer if necessary
  • 5 Asynchronously reads from underlying stream
  • 6 Configures await operation to ignore context
  • 7 Indicates end of stream where appropriate
  • 8 Returns the next byte from the buffer
  • 9 Uses the byte in some way

For the moment, you can ignore the ConfigureAwait call within ReadByteAsync. You’ll come back to that in section 5.10, when you look at how to use async/await effectively. The rest of the code is straightforward, and all of it could be written without ValueTask<TResult>; it’d just be much less efficient.

In this case, most invocations of our ReadByteAsync method wouldn’t even use the await operator because you’d still have buffered data to return, but it’d be equally useful if you were awaiting another value that’s usually complete immediately. As I explained in section 5.6.2, when you await an operation that’s already complete, the execution continues synchronously, which means you don’t need to schedule a continuation and can avoid object allocations.

This is a simplified version of a prototype of the CodedInputStream class from the Google.Protobuf package, the .NET implementation of Google’s Protocol Buffers serialization protocol. In reality, there are multiple methods, each reading a small amount of data either synchronously or asynchronously. Deserializing a message with lots of integer fields can involve a lot of method calls, and making the asynchronous methods return a Task<TResult> each time would’ve been prohibitively inefficient.

Note

You may be wondering what to do if you have an async method that doesn’t return a value (so would normally have a return type of Task), but that still falls into the category of completing without having to schedule any continuations. In this case, you can stick to returning Task: the async/await infrastructure caches a task that it can return from any async method declared to return Task that completes synchronously and without an exception. If the method completes synchronously but with an exception, the cost of allocating a Task object likely will be dwarfed by the exception overhead anyway.

For most of us, the ability to use ValueTask<TResult> as a return type for async methods is the real benefit of C# 7 in terms of asynchrony. But this has been implemented in a general-purpose way, allowing you to create your own return types for async methods.

5.8.2. The 0.1% case: Building your own custom task type

I’d like to emphasize again that you’re almost certainly never going to need this information. I’m not going to even try to provide a use case beyond ValueTask<TResult>, because anything I could think of would be obscure. That said, this book would be incomplete if I didn’t show the pattern the compiler uses to determine that a type is a task type. I’ll show the details of how the compiler uses the pattern in the next chapter, when you look at the code that gets generated for an async method.

Obviously, a custom task type has to implement the awaitable pattern, but there’s much more to it than that. To create a custom task type, you have to write a corresponding builder type and use the System.Runtime.CompilerServices.AsyncMethodBuilderAttribute to let the compiler know the relationship between the two types. This is a new attribute available in the same NuGet package as ValueTask<TResult>, but if you don’t want the extra dependency, you can include your own declaration of the attribute (in the right namespace and with the appropriate BuilderType property). The compiler will then accept that as a way of decorating task types.

The task type can be generic in a single type parameter or nongeneric. If it’s generic, that type parameter must be the type of GetResult in the awaiter type; if it’s nongeneric, GetResult must have a void return type.[2] The builder must be generic or nongeneric in the same way as the task type.

2

This surprised me somewhat. It means you can’t write a custom task type that always represents an operation returning a string, for example. Given how niche the whole feature is, the likelihood of anyone really wanting a niche use case within the feature is pretty small.

The builder type is the part where the compiler interacts with your code when it’s compiling a method returning your custom type. It needs to know how to create your custom task, propagate completion or exceptions, resume after a continuation, and so on. The set of methods and properties you need to provide is significantly more complex than the awaitable pattern. It’s easiest to show a complete example, in terms of the members you need to provide, without any implementation.

Listing 5.11. Skeleton of the members required for a generic task type
[AsyncMethodBuilder(typeof(CustomTaskBuilder<>))]
public class CustomTask<T>
{
    public CustomTaskAwaiter<T> GetAwaiter();
}

public class CustomTaskAwaiter<T> : INotifyCompletion
{
    public bool IsCompleted { get; }
    public T GetResult();
    public void OnCompleted(Action continuation);
}

public class CustomTaskBuilder<T>
{
    public static CustomTaskBuilder<T> Create();

    public void Start<TStateMachine>(ref TStateMachine stateMachine)
        where TStateMachine : IAsyncStateMachine;

    public void SetStateMachine(IAsyncStateMachine stateMachine);
    public void SetException(Exception exception);
    public void SetResult(T result);

    public void AwaitOnCompleted<TAwaiter, TStateMachine>
        (ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine;

    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>
        (ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine;

    public CustomTask<T> Task { get; }
}

This code shows a generic custom task type. For a nongeneric type, the only difference in the builder would be that SetResult would be a parameterless method.

One interesting requirement is the AwaitUnsafeOnCompleted method. As you’ll see in the next chapter, the compiler has the notion of safe awaiting and unsafe awaiting, where the latter relies on the awaitable type to handle context propagation. A custom task builder type has to handle resuming from both kind of awaiting.

Note

The term unsafe here isn’t directly related to the unsafe keyword, although similarities exist in terms of “here be dragons, take care!”

To reiterate one final time, you almost certainly don’t want to be doing this except as a matter of interest. I don’t expect to ever implement my own task type for production code, but I’ll certainly use ValueTask<TResult>, so I’m still grateful that the feature exists.

Speaking of useful new features, C# 7.1 has one additional feature to mention. Fortunately, it’s considerably simpler than custom task types.

5.9. Async main methods in C# 7.1

The requirements for the entry point have remained the same in C# for a long time:

  • It must be a method called Main.
  • It must be static.
  • It must have a void or int return type.
  • It must either be parameterless or have a single (non-ref, non-out) parameter of type string[].
  • It must be nongeneric and declared in a nongeneric type (including any containing types being nongeneric, if it’s declared in a nested type).
  • It can’t be a partial method without implementation.
  • It can’t have the async modifier.

With C# 7.1, the final requirement has been dropped but with a slightly different requirement around the return type. In C# 7.1, you can write an async entry point (still called Main, not MainAsync), but it has to have a return type of either Task or Task<int> corresponding to a synchronous return type of void or int. Unlike most async methods, an async entry point can’t have a return type of void or use a custom task type.

Beyond that, it’s a regular async method. For example, the following listing shows an async entry point that prints two lines to the console with a delay between them.

Listing 5.12. A simple async entry point
static async Task Main()
{
    Console.WriteLine("Before delay");
    await Task.Delay(1000);
    Console.WriteLine("After delay");
}

The compiler handles async entry points by creating a synchronous wrapper method that it marks as the real entry point into the assembly. The wrapper method is either parameterless or has a string[] parameter and either returns void or int, depending on what the async entry point has in terms of parameters and return type. The wrapper method calls the real code and then calls GetAwaiter() on the returned task and GetResult() on the awaiter. For example, the wrapper method generated for listing 5.11 would look something like this:

static void <Main>()                   1
{
    Main().GetAwaiter().GetResult();
}

  • 1 Method has a name that’s invalid in C# but valid in IL.

Async entry points are handy for writing small tools or exploratory code that uses an async-oriented API such as Roslyn.

Those are all the async features from a language perspective. But knowing the capabilities of the language is different from knowing how to use those capabilities effectively. That’s particularly true for asynchrony, which is an inherently complex topic.

5.10. Usage tips

This section could never be a complete guide to using asynchrony effectively; that could fill an entire book on its own. We’re coming to the end of a chapter that’s already long, so I’ve restrained myself to offer just the most important tips in my experience. I strongly encourage you to read the perspectives of other developers. In particular, Stephen Cleary and Stephen Toub have written reams of blog posts and articles that go into many aspects in great depth. In no particular order, this section provides the most useful suggestions I can make reasonably concisely.

5.10.1. Avoid context capture by using ConfigureAwait (where appropriate)

In sections 5.2.2 and 5.6.2, I described synchronization contexts and their effect on the await operator. For example, if you’re running on a UI thread in WPF or Win-Forms and you await an asynchronous operation, the UI synchronization context and the async infrastructure make sure that the continuation that runs after the await operator still runs on that same UI thread. That’s exactly what you want in UI code, because you can then safely access the UI afterward.

But when you’re writing library code—or code in an application that doesn’t touch the UI—you don’t want to come back to the UI thread, even if you were originally running in it. In general, the less code that executes in the UI thread, the better. This allows the UI to update more smoothly and avoids the UI thread being a bottleneck. Of course, if you’re writing a UI library, you probably do want to return to the UI thread, but most libraries—for business logic, web services, database access and the like—don’t need this.

The ConfigureAwait method is designed precisely for this purpose. It takes a parameter that determines whether the returned awaitable will capture the context when it’s awaited. In practice, I think I’ve always seen the value false passed in as an argument. In library code, you wouldn’t write the page-length-fetching code as you saw it earlier:

static async Task<int> GetPageLengthAsync(string url)
{
    var fetchTextTask = client.GetStringAsync(url);
    int length = (await fetchTextTask).Length;
                                                   1
    return length;
}

  • 1 Imagine more code here

Instead, you’d call ConfigureAwait(false) on the task returned by client.GetStringAsync(url) and await the result:

static async Task<int> GetPageLengthAsync(string url)
{
    var fetchTextTask = client.GetStringAsync(url).ConfigureAwait(false);
    int length = (await fetchTextTask).Length;
                                                   1
    return length;
}

  • 1 Same additional code

I’ve cheated a little here by using implicit typing for the fetchTextTask variable. In the first example, it’s a Task<int>; in the second, it’s a ConfiguredTaskAwaitable<int>. Most code I’ve seen awaits the result directly anyway, though, like this:

string text = await client.GetStringAsync(url).ConfigureAwait(false);

The result of calling ConfigureAwait(false) is that the continuation won’t be scheduled against the original synchronization context; it’ll execute on a thread-pool thread. Note that the behavior differs from the original code only if the task hasn’t already completed by the time it’s awaited. If it has already completed, the method continues executing synchronously, even in the face of ConfigureAwait(false). Therefore, every task you await in a library should be configured like this. You can’t just call ConfigureAwait(false) on the first task in an async method and rely on the rest of the method executing on a thread-pool thread.

All of this means you need to be careful when writing library code. I expect that eventually a better solution may exist (setting the default for a whole assembly, for example), but for the moment, you need to be vigilant. I recommend using a Roslyn analyzer to spot where you’ve forgotten to configure a task before awaiting it. I’ve had positive experiences with the ConfigureAwaitChecker.Analyzer NuGet package, but others are available, too.

In case you’re worried about what this does to the caller, you don’t need to be. Suppose the caller is awaiting the task returned by GetPageLengthAsync and then updating a user interface to display the result. Even if the continuation within GetPageLengthAsync runs on a thread-pool thread, the await expression performed in the UI code will capture the UI context and schedule its continuation to run on the UI thread, so the UI can still be updated after that.

5.10.2. Enable parallelism by starting multiple independent tasks

In section 5.6.1, you looked at multiple pieces of code to achieve the same goal: find out how much to pay an employee based on their hourly rate and how many hours they’d worked. The last two pieces of code were like this:

Task<decimal> hourlyRateTask = employee.GetHourlyRateAsync();
decimal hourlyRate = await hourlyRateTask;
Task<int> hoursWorkedTask = timeSheet.GetHoursWorkedAsync(employee.Id);
int hoursWorked = await hoursWorkedTask;
AddPayment(hourlyRate * hoursWorked);

and this

Task<decimal> hourlyRateTask = employee.GetHourlyRateAsync();
Task<int> hoursWorkedTask = timeSheet.GetHoursWorkedAsync(employee.Id);
AddPayment(await hourlyRateTask * await hoursWorkedTask);

In addition to being shorter, the second piece of code introduces parallelism. Both tasks can be started independently, because you don’t need the output of the second task as input into the first task. This doesn’t mean that the async infrastructure creates any more threads. For example, if the two asynchronous operations here are web services, both requests to the web services can be in flight without any threads being blocked on the result.

The shortness aspect is only incidental here. If you want the parallelism but like having the separate variables, that’s fine:

Task<decimal> hourlyRateTask = employee.GetHourlyRateAsync();
Task<int> hoursWorkedTask = timeSheet.GetHoursWorkedAsync(employee.Id);
decimal hourlyRate = await hourlyRateTask;
int hoursWorked = await hoursWorkedTask;
AddPayment(hourlyRate * hoursWorked);

The only difference between this and the original code is that I swapped the second and third lines. Instead of awaiting hourlyRateTask and then starting hoursWorkedTask, you start both tasks and then await both tasks.

In most cases, if you can perform independent work in parallel, it’s a good idea to do so. Be aware that if hourlyRateTask fails, you won’t observe the result of hoursWorkedTask, including any failures in that task. If you need to log all task failures, for example, you might want to use Task.WhenAll instead.

Of course, this sort of parallelization relies on the tasks being independent to start with. In some cases, the dependency may not be entirely obvious. If you have one task that’s authenticating a user and another task performing an action on their behalf, you’d want to wait until you’d checked the authentication before starting the action, even if you could write the code to execute in parallel. The async/await feature can’t make these decisions for you, but it makes it easy to parallelize asynchronous operations when you’ve decided that it’s appropriate.

5.10.3. Avoid mixing synchronous and asynchronous code

Although asynchrony isn’t entirely all or nothing, it gets much harder to implement correctly when some of your code is synchronous and other parts are asynchronous. Switching between the two approaches is fraught with difficulties—some subtle, others less so. If you have a network library that exposes only synchronous operations, writing an asynchronous wrapper for those operations is difficult to do safely, and likewise in reverse.

In particular, be aware of the dangers of using the Task<TResult>.Result property and Task.Wait() methods to try to synchronously retrieve the result of an asynchronous operation. This can easily lead to deadlock. In the most common case, the asynchronous operation requires a continuation to execute in a thread that’s blocked, waiting for the operation to compete.

Stephen Toub has a pair of excellent and detailed blog posts on this topic: “Should I expose synchronous wrappers for asynchronous methods?” and “Should I expose asynchronous wrappers for synchronous methods?” (Spoiler alert: the answer is no in both cases, as you’ve probably guessed.) As with all rules, there are exceptions, but I strongly advise that you make sure you thoroughly understand the rule before breaking it.

5.10.4. Allow cancellation wherever possible

Cancellation is one area that doesn’t have a strong equivalent in synchronous code, where you usually have to wait for a method to return before continuing. The ability to cancel an asynchronous operation is extremely powerful, but it does rely on cooperation throughout the stack. If you want to use a method that doesn’t allow you to pass in a cancellation token, there’s not an awful lot you can do about it. You can write somewhat-intricate code so that your async method completes with a canceled status and ignore the final result of the noncancelable task, but that’s far from ideal. You really want to be able to stop any work in progress, and you also don’t want to have to worry about any disposable resources that could be returned by the asynchronous method when it does eventually complete.

Fortunately, most low-level asynchronous APIs do expose a cancellation token as a parameter. All you need to do is follow the same pattern yourself, typically passing the same cancellation token you receive in the parameter as an argument to all asynchronous methods you call. Even if you don’t currently have any requirements to allow cancellation, I advise providing the option consistently right from the start, because it’s painful to add later.

Again, Stephen Toub has an excellent blog post on the subtle difficulties of trying to work around noncancelable asynchronous operations. Search for “How do I cancel non-cancelable async operations?” to find it.

5.10.5. Testing asynchrony

Testing asynchronous code can be extremely tricky, particularly if you want to test the asynchrony itself. (Tests that answer questions such as “What happens if I cancel the operation between the second and third asynchronous calls within the method?” require quite elaborate work.)

It’s not impossible, but be prepared for an uphill battle if you want to test comprehensively. When I wrote the third edition of this book, I hoped that by 2019 there would be robust frameworks to make all of this relatively simple. Unfortunately, I’m disappointed.

Most unit-test frameworks do have support for asynchronous tests, however. That support is pretty much vital to write tests for asynchronous methods, for all the reasons I mentioned before about the difficulties in mixing synchronous and asynchronous code. Typically, writing an asynchronous test is as simple as writing a test method with the async modifier and declaring it to return Task instead of void:

[Test]
public async Task FooAsync()
{
                            1
}

  • 1 Code to test your FooAsync production method

Test frameworks often provide an Assert.ThrowsAsync method for testing that a call to an asynchronous method returns a task that eventually becomes faulted.

When testing asynchronous code, often you’ll want to create a task that’s already completed, with a particular result or fault. The methods Task.FromResult, Task.FromException, and Task.FromCanceled are useful here.

For more flexibility, you can use TaskCompletionSource<TResult>. This type is used by a lot of the async infrastructure in the framework. It effectively allows you to create a task representing an ongoing operation and then set the result (including any exception or cancellation) later, at which point the task will complete. This is extremely useful when you want to return a task from a mocked dependency but make that returned task complete later in the test.

One aspect of TaskCompletionSource<TResult> to know about is that when you set the result, continuations attached to the associated task can run synchronously on the same thread. The exact details of how the continuations are run depend on various aspects of the threads and synchronization contexts involved, and after you’re aware of it as a possibility, it’s relatively easy to take account of. Now you’re aware and can hopefully avoid wasting the time being baffled in the way that I was.

This is an incomplete summary of what I’ve learned over the last four years or so of writing asynchronous code, but I don’t want to lose sight of the topic of the book (the C# language, not asynchrony). You’ve seen what the async/await feature does, from the developer’s perspective. You haven’t looked at what happens under the hood in any detail yet, although the awaitable pattern provides some clues.

If you haven’t played with async/await yet, I strongly advise that you do so now, before taking on the next chapter, which looks at the implementation details. Those details are important but are tricky to understand at the best of times and will be hard to understand if you don’t have some experience of using async/await. If you don’t have that experience yet and don’t particularly want to put the time in right now, I advise skipping the next chapter for now. It’s only about the implementation details of asynchrony; I promise you won’t miss anything else.

Summary

  • The core of asynchrony is about starting an operation and then later continuing when the operation has completed without having to block in the middle.
  • Async/await allows you to write familiar-looking code that acts asynchronously.
  • Async/await handles synchronization contexts so UI code can start an asynchronous operation and then continue on the UI thread when that operation has finished.
  • Successful results and exceptions are propagated through asynchronous operations.
  • Restrictions limit where you can use the await operator, but C# 6 (and later) versions have fewer restrictions than C# 5.
  • The compiler uses the awaitable pattern to determine which types can be awaited.
  • C# 7 allows you to create your own custom task type, but you almost certainly want to use ValueTask<TResult>.
  • C# 7.1 allows you to write async Main methods as program entry points.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset