Creating awaitable types

If you’ve used async/await in your applications, you’ll generally await a Task, but you can actually make your own type awaitable. Let’s look at how we’d do this.

Why are Task’s awaitable?

A Task is awaitable, not because it’s a Task type, but its because it supports a specific method name which returns a TaskAwaiter or TaskAwaiter for the Task type. So let’s take a look at what this method looks like

public TaskAwaiter GetAwaiter()

So it’s really that simple, if we want to make our object awaitable we simply define the same method in our type.

What is a TaskAwaiter?

A TaskAwaiter is a struct which implements the ICriticalNotifyCompletion interface and the INotifyCompletion interface (the ICriticalNotifyCompletion derives from the INotifyCompletion). But this still wouldn’t make the TaskAwaiter awaitable it also needs the property IsCompleted and the method GetResult – more on this in “the rules for an “Awaiter” type” below.

It does seem strange that these methods weren’t all put into a single interface, but there you have it.

How do I create a bare minimum awaitable type?

Let’s first review the rules for making our type awaitable

  • Our type should have a method named GetAwaiter which takes no arguments and returns a suitable “Awaiter” type

and now the rules for an “Awaiter” type

  • The type should implement the INotifyCompletion interface or the ICriticalNotifyCompletion interface
  • It should have a IsCompleted property of type boolean
  • It should have a GetResult method which returns void or a result value
    • and now let’s put this together into a somewhat pointless type which demonstrates a bare minimum set of requirements to get this type to be awaitable

      public class MyObject
      {
         public MyAwaiter GetAwaiter()
         {
            return new MyAwaiter();
         }
      }
      
      public struct MyAwaiter
      {
         public void OnCompleted(Action continuation)
         {
            continuation();
         }
      
         public bool IsCompleted { get; private set; }
      
         public void GetResult()
         {			
         }
      }
      

      and now in use, we would have something like this

      var o = new MyObject();
      await o;
      

      Note: if you do not call the conitnuation() action in the OnCompleted method the code will block indefinitely.

      The flow of this code goes as follows…

      await o is called, this calls GetAwaiter on MyObject which returns a MyAwaiter. Next IsCompleted is checked, if the awaiter has already completed then control returns to the line after the await, if it’s false OnCompleted is called which basically blocks until the continuation is called, finally the GetResult method is called.

      Extension methods that are awaitable

      So we’ve discussed creating our own type, making it awaitable and even creating an awaiter type, but another approach which one might use is creating extension methods and instead of going to the hassle of creating an awaiter type, we’ll simply reuse the TaskCompletionSource type which can be used as a puppet task.

      Let’s go straight to some code which I’ve unashamedly lifted from Stephen Toub’s blog post await anything.

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

      and we would use this as follows

      await Process.Start(“notepad.exe”)
      

      Looking at the extension method you can see we’re going to use the TaskCompletion classes GetAwaiter to return the awaiter type (in this case a TaskAwaiter) and we simply try to set the result on the TaskCompletionSource if, or when, the process exits.

      References

      await anything
      Design change: new “await” pattern for greater efficiency