Implementing Then with Await

Stephen Toub - MSFT

In a post a while ago, I talked about sequential composition of asynchronous operations.  Now that we have the async/await keywords in C# and Visual Basic, such composition is trivial, and async/await are indeed the recommended way to achieve such composition with these languages.

However, in that post I also described a few “Then” methods (similar methods are now available in JavaScript and VC++), and I thought it’d be fun to quickly show how Then can be implemented in terms of await. We’d probably want to support Then methods that operate on either Task or Task<TResult>, and then run a continuation delegate that returns either void or TNewResult.  This leads to four overloads, each of which can be implemented with just one or two lines of code:

public static async Task Then(
    this Task antecedent, Action continuation)
{
    await antecedent;
    continuation();
}

public static async Task<TNewResult> Then<TNewResult>(
    this Task antecedent, Func<TNewResult> continuation)
{
    await antecedent;
    return continuation();
}

public static async Task Then<TResult>(
    this Task<TResult> antecedent, Action<TResult> continuation)
{
    continuation(await antecedent);
}

public static async Task<TNewResult> Then<TResult,TNewResult>(
    this Task<TResult> antecedent, Func<TResult,TNewResult> continuation)
{
    return continuation(await antecedent);
}

Simple, right?  In each case, we just await the supplied task and then run the supplied continuation delegate (potentially passing in the result of the antecedent task).  Exception handling is all handled correctly here as well, which is extra exciting since we didn’t have to do anything special with regards to exceptions: any exceptions from either a non-successful antecedent task or from invoking the continuation delegate are automatically stored into the task returned from Then.

There are a few additional overloads we might also want.  These previous overloads work with continuation functions that return void or TNewResult, rather than Task or Task<TNewResult>.  If you want to use await within the continuation functions, or if you otherwise want to return a Task from the delegate, additional overloads could help to automatically unwrap the task.  As before, these Then overloads are simple to write, with just a few tweaks from the previously shown overloads:

public static async Task Then(
    this Task task, Func<Task> continuation)
{
    await task;
    await continuation();
}

public static async Task<TNewResult> Then<TNewResult>(
    this Task task, Func<Task<TNewResult>> continuation)
{
    await task;
    return await continuation();
}

public static async Task Then<TResult>(
    this Task<TResult> task, Func<TResult,Task> continuation)
{
    await continuation(await task);
}

public static async Task<TNewResult> Then<TResult, TNewResult>(
    this Task<TResult> task, Func<TResult, Task<TNewResult>> continuation)
{
    return await continuation(await task);
}

0 comments

Discussion is closed.

Feedback usabilla icon