Implementing Then with Await

Implementing Then with Await

Rate This
  • Comments 6

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);
}

Leave a Comment
  • Please add 3 and 3 and type the answer here:
  • Post
  • Such beautiful examples of asynchronous code make node.js appear to be an absurd programming environment in contrast.

  • I'm sorry that I am so dense.

    Where do this definitions live? In their own class or in the class using the sequential tasks?

    Thanks for all the help.

    Regards,

    Chris

    chris AT wegenerconsulting DOT com

  • @Chris Wegener: These would just be helper functions you'd put into whatever class you wanted, e.g. if you had a class of extension methods, you could dump them there:

    public static class MyTaskExtensions

    {

       public static async Task Then(this Task antecedent, Action continuation)

       {

           await antecedent;

           continuation();

       }

       ...

    }

  • Thanks, that explains it!

    Agin sorry for being dense I should have been able to figure that out. ;-)

  • Can we see some real-world usage example of this?

  • @undecided: Now that C# and VB have the async and await keywords, there aren't that many cases where such Then methods would be extremely useful in "real-world usage".  This post was mainly to highlight how Then and await relate, so that folks coming from C++ or JavaScript where these Then methods do exist can better understand the relationship.  That said, you might imagine a few cases where such helpers would be useful in simplifying code.  Imagine you had a method called DownloadStringAsync, which accepted a URL and returned the HTML contents of the target.  Now imagine you wanted two download two pages, but you wanted the results in all uppercase.  You might write that as:

       string [] results = await Task.WhenAll(

           DownloadStringAsync(first).Then(str => str.ToUpper()),

           DownloadStringAsync(second).Then(str => str.ToUpper()));

    making it easy to potentially run the conversions in parallel.  As is highlighted in this post, though, Then is trivially implemented in terms of await, so there's nothing you can do with Then you can't also do with await relatively easily, and conversely there are many things you can do with await that are not easy using Then, namely anything that involves any more complicated control flow than a simple sequence of well-defined operations.

Page 1 of 1 (6 items)