await anything;

Stephen Toub - MSFT

One of the very cool things about the new await keyword in C# and Visual Basic is that it’s pattern based.  It works great with Task and Task<TResult>, and awaiting those two types will represent the vast majority of uses, but they’re by no means the only types that can be awaited.  The languages support awaiting any instance that exposes the right method (either instance method or extension method): GetAwaiter.  A GetAwaiter needs to implement the INotifyCompletion interface (and optionally the ICriticalNotifyCompletion interface) and return a type that itself exposes three members:

bool IsCompleted { get; }
void OnCompleted(Action continuation);
TResult GetResult(); // TResult can also be void

As an example of this, Task’s GetAwaiter method returns a value of type TaskAwaiter:

public struct TaskAwaiter : ICriticalNotifyCompletion

and that’s what enables awaiting the Task.  This is a simplification, but in short the OnCompleted method registers the Action as a continuation onto the Task (e.g. with ContinueWith), such that when the task completes, it will cause the compiler-generated state machine around the await to pick back up where it left off.

The title of this post is “await anything;”, so let’s see how we can await things besides Task and Task<TResult>.  To do that, we’ll need appropriate “awaiter” types for the “awaitable” type to await.  That doesn’t mean we have to write new “awaiter” types, however.  There are really two different approaches to making something awaitable: develop a new awaiter type that exposes the right pattern, or figure out how to create a Task or Task<TResult> from the thing being awaited, and then just reuse Task or Task<TResult>’s awaiter.  For the majority of cases, the latter approach is very straightforward, so we’ll start with that.

Let’s say you want to be able to write code like:

await TimeSpan.FromMinutes(15);

in order to asynchronously pause for 15 minutes.  To do that, we can develop a 1-line GetAwaiter method for TimeSpan:

public static TaskAwaiter GetAwaiter(this TimeSpan timeSpan)
{
    return TaskEx.Delay(timeSpan).GetAwaiter();
}

That’s it.  Or let’s say we like waiting for periods of time so much, that we want to simply this down to just:

await 15000; // in milliseconds

No problem, we can do that with another one-line awaiter:

public static TaskAwaiter GetAwaiter(this Int32 millisecondsDue)
{
    return TimeSpan.FromMilliseconds(millisecondsDue).GetAwaiter();
}

Let’s say we like waiting for time-like things so much that we want to be able to wait until a particular date/time, ala

await DateTimeOffset.UtcNow.AddMinutes(1);

Again, piece of cake:

public static TaskAwaiter GetAwaiter(this DateTimeOffset dateTimeOffset)
{
    return (dateTimeOffset – DateTimeOffset.UtcNow).GetAwaiter();
}

Tired of time?  Alright.  The GetAwaiter function for Task allows you to wait for a single task, how about enabling waiting for an enumerable of tasks so that you can write code like:

await from url in urls select DownloadAsync(url);

Easy peasy:

public static TaskAwaiter GetAwaiter(this IEnumerable<Task> tasks)
{
    return TaskEx.WhenAll(tasks).GetAwaiter();
}

All of the examples thus far were one-liners because we already have a function that takes the input to the extension method and produces a task from it.  However, with just a few more lines, you can convert almost anything that has some notion of future completion into a task, through the TaskCompletionSource<TResult> type.  If you can express your need by completing the statement “I want to await until …” or “I want the await to complete when …”, this is likely a good approach for you.

As an example, consider wanting to spin up another process and then asynchronously wait for that process to complete, e.g.

await Process.Start(“Foo.exe”);

You could do that with a GetAwaiter method like the following:

public static TaskAwaiter<int> GetAwaiter(this Process process)
{
    var tcs = new TaskCompletionSource<int>();
    process.EnableRaisingEvents = true;
    process.Exited += (s, e) => tcs.TrySetResult(process.ExitCode);
    if (process.HasExited) tcs.TrySetResult(process.ExitCode);
    return tcs.Task.GetAwaiter();
}

Or maybe you want to asynchronously wait until cancellation is requested, e.g.

await cancellationToken;

That could be done with a GetAwaiter like the following:

public static TaskAwaiter GetAwaiter(this CancellationToken cancellationToken)
{
    var tcs = new TaskCompletionSource<bool>();
    Task t = tcs.Task;
    if (cancellationToken.IsCancellationRequested) tcs.SetResult(true);
    else cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
    return t.GetAwaiter();
}

You get the idea.

The second approach to making an awaitable type is to implement a custom awaiter.  This could either be a separate type that’s returned by GetAwaiter and that exposes the IsCompleted/OnCompleted/GetResult members, or it could be a GetAwaiter method that returns “this”, with IsCompleted/OnCompleted/GetResult also exposed on the awaitable type.  You’d typically go this route if you can’t express your desire as “I want the await to complete when…”, but rather as “When the await completes, I want to continue executing …”, filling in the blank for that “…”.  In particular, you’d need to use this approach if you need full control over how (rather than when) the “Action continuation” delegate is invoked.

Imagine, for example, that you wanted to launch some work to run on the ThreadPool.  This work would compute a string and then store the result into a control on your UI.  To modify the control, you need to be on the UI thread, so you somehow need to transition to the UI thread to do that work.  If this were, for example, a Windows Forms application, we could accomplish this by building an awaiter for a Windows Forms Control.  That would allow us to write code like:

ThreadPool.QueueUserWorkItem(async delegate
{
    string text = ComputeString();
    await button1;
    button1.Text = text;
});

We want the operation of awaiting the button1 to transition to the UI thread and then continue the execution there.  We can do that with an implementation like the following:

public static ControlAwaiter GetAwaiter(this Control control)
{
    return new ControlAwaiter(control);
}

public struct ControlAwaiter : INotifyCompletion
{
    private readonly Control m_control;

    public ControlAwaiter(Control control)
    { 
        m_control = control;
    }

    public bool IsCompleted
    { 
        get { return !m_control.InvokeRequired; }
    }

    public void OnCompleted(Action continuation)
    { 
        m_control.BeginInvoke(continuation); 
    }

    public void GetResult() { }
}

You can also combine these approaches, such as by writing a custom awaiter which wraps the awaiter for a task, layering on additional functionality.  For example, culture information is not flowed by default as part of ExecutionContext, which is the standard .NET mechanism for transferring important environmental information across asynchronous invocations.  What if we wanted to make it easy to flow culture?  Imagine the following syntax for awaiting a task with the flow of culture:

await task.WithCulture();

We could enable that with code like the following:

public static CultureAwaiter WithCurrentCulture(this Task task)
{
    return new CultureAwaiter(task);
}

public class CultureAwaiter : INotifyCompletion
{
    private readonly TaskAwaiter m_awaiter;
    private CultureInfo m_culture;

    public CultureAwaiter(Task task)
    {
        if (task == null) throw new ArgumentNullException(“task”);
        m_awaiter = task.GetAwaiter();
    }

    public CultureAwaiter GetAwaiter() { return this; }

    public bool IsCompleted { get { return m_awaiter.IsCompleted; } }

    public void OnCompleted(Action continuation)
    {
        m_culture = Thread.CurrentThread.CurentCulture;
 
        m_awaiter.OnCompleted(continuation);
    }

    public void GetResult()
    {
        if (m_culture != null) 
Thread.CurrentThread.CurrentCulture = m_culture; 

        m_awaiter.GetResult();
    }
}

This awaiter implementation wraps a TaskAwaiter, and this implementation’s IsCompleted, OnCompleted, and GetResult members delegate to the contained TaskAwaiter’s.  On top of that, though, the implementation captures the current culture in OnCompleted and then restores it in GetResult.

By now, it should be obvious that there are loads of interesting possibilities here.  I look forward to seeing all the interesting and useful awaiters you come up with.  Just keep in mind that while there are plenty of “cool” things you can do, code readability and maintainability is really important, so make sure that the coolness isn’t trumped by lack of clarity about the code’s meaning.

[NOTE 4/09/2012: This blog post was originally based on the original Async CTP released in Oct 2010.  Visual Studio 11 Beta released in Feb 2012 uses an updated awaiter pattern, and this post has now been updated to conform to that pattern.]

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • lars larsson 0

    Is it possible to create a backflow from task/async? E.g. a method that loads settings & changing culture – this is stripped once you get back from it.

    Attempted to curry the continuation action & allow backflow of current Principal & seems to work as long as something acutally async & not Task.Result then OnCompleted is never called as shown by pseudo code https://devblogs.microsoft.com/pfxteam/asyncawait-faq/

    Never ever do it or is there a “pattern”/recommendation on how to do it?
    Especially when there is a deepth of calls it would be nice to just know its there & working without to much special code.

Feedback usabilla icon