Understanding C# async / await (2) The Awaitable-Awaiter Pattern
Understanding C# async / await:
- Understanding C# async / await (1) Compilation
- Understanding C# async / await (2) Awaitable-Awaiter Pattern
- Understanding C# async / await (3) Runtime Context
What is awaitable
Part 1 shows that any Task is awaitable. Actually there are other awaitable types. Here is an example:
Task<int> task = new Task<int>(() => 0); int result = await task.ConfigureAwait(false); // Returns a ConfiguredTaskAwaitable<TResult>.
The returned ConfiguredTaskAwaitable<TResult> struct is awaitable. And it is not Task at all:
public struct ConfiguredTaskAwaitable<TResult> { private readonly ConfiguredTaskAwaiter m_configuredTaskAwaiter;</span><span style="color: blue;">internal </span><span style="color: black;">ConfiguredTaskAwaitable(</span><span style="color: #2b91af;">Task</span><span style="color: black;"><TResult> task, </span><span style="color: blue;">bool </span><span style="color: black;">continueOnCapturedContext) { </span><span style="color: blue;">this</span><span style="color: black;">.m_configuredTaskAwaiter = </span><span style="color: blue;">new </span><span style="color: #2b91af;">ConfiguredTaskAwaiter</span><span style="color: black;">(task, continueOnCapturedContext); } </span><span style="color: blue;">public </span><span style="color: #2b91af;">ConfiguredTaskAwaiter </span><span style="color: black;">GetAwaiter() { </span><span style="color: blue;">return this</span><span style="color: black;">.m_configuredTaskAwaiter; }
}
It has one GetAwaiter() method. Actually in part 1 we have seen that Task has GetAwaiter() method too:
public class Task { public TaskAwaiter GetAwaiter() { return new TaskAwaiter(this); } } public class Task<TResult> : Task { public new TaskAwaiter<TResult> GetAwaiter() { return new TaskAwaiter<TResult>(this); } }
Task.Yield() is another example:
await Task.Yield(); // Returns a YieldAwaitable.
The returned YieldAwaitable is not Task either:
public struct YieldAwaitable { public YieldAwaiter GetAwaiter() { return default(YieldAwaiter); } }
Again, it just has one GetAwaiter() method. This article will look at what is awaitable.
The awaitable-awaiter pattern
By observing different awaitable / awaiter types, we can tell that an object is awaitable if
- It has a GetAwaiter() method (instance method or extension method);
- Its GetAwaiter() method returns an awaiter. An object is an awaiter if:
- It implements INotifyCompletion or ICriticalNotifyCompletion interface;
- It has an IsCompleted, which has a getter and returns a Boolean;
- it has a GetResult() method, which returns void, or a result.
So apparently this awaitable-awaiter pattern is very similar to the iteratable-iterator pattern. Here is the interface definitions of iteratable / iterator:
public interface IEnumerable { IEnumerator GetEnumerator(); } public interface IEnumerator { object Current { get; }</span><span style="color: blue;">bool </span><span style="color: black;">MoveNext(); </span><span style="color: blue;">void </span><span style="color: black;">Reset();
}
public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); } public interface IEnumerator<out T> : IDisposable, IEnumerator { T Current { get; } }
In case the out keyword does not sound familiar, please find detailed explanation in another article Understanding C# Covariance And Contravariance (2) Interfaces.
The “missing” IAwaitable / IAwaiter interfaces
Similar to IEnumerable and IEnumerator interfaces, awaitable / awaiter can be visualized by IAwaitable / IAwaiter interfaces too. This is the non-generic version:
public interface IAwaitable { IAwaiter GetAwaiter(); } public interface IAwaiter : INotifyCompletion // or ICriticalNotifyCompletion { // INotifyCompletion has one method: void OnCompleted(Action continuation);// ICriticalNotifyCompletion implements INotifyCompletion, // also has this method: void UnsafeOnCompleted(Action continuation); </span><span style="color: blue;">bool </span><span style="color: black;">IsCompleted { </span><span style="color: blue;">get</span><span style="color: black;">; } </span><span style="color: blue;">void </span><span style="color: black;">GetResult();
}
Please notice GetResult() returns void here. Task.GetAwaiter() / TaskAwaiter.GetResult() is of such case.
And here comes the generic version:
public interface IAwaitable<out TResult> { IAwaiter<TResult> GetAwaiter(); } public interface IAwaiter<out TResult> : INotifyCompletion // or ICriticalNotifyCompletion { bool IsCompleted { get; }TResult GetResult();
}
Here the only difference is, GetResult() return a result. Task<TResult>.GetAwaiter() / TaskAwaiter<TResult>.GetResult() is of this case.
Please notice .NET core does not define these IAwaitable / IAwaiter interfaces at all. IAwaitable interface will constraint GetAwaiter() to be instance method. Actually C# supports both GetAwaiter() instance method and GetAwaiter() extension method.
Here these interfaces are used only for better visualizing what is awaitable / awaiter. Now, if looking at above ConfiguredTaskAwaitable / ConfiguredTaskAwaiter, YieldAwaitable / YieldAwaiter, Task / TaskAwaiter pairs again, they all “implicitly” implement these “missing” IAwaitable / IAwaiter interfaces. The rest part of this article will show how to implement awaitable / awaiter.
Await any function / action
In C# await cannot be used with lambda. This code:
int result = await (() => 0);
will cause a compiler error:
Cannot await 'lambda expression'
This is easy to understand because this lambda expression (() => 0) may be a function or a expression tree. Obviously we mean function here, and we can tell compiler in this way:
int result = await new Func<int>(() => 0);
It causes an different error:
Cannot await 'System.Func<int>'
OK, now the compiler is complaining the type instead of syntax. With the understanding of the awaitable / awaiter pattern, Func<TResult> type can be easily made into awaitable.
GetAwaiter() instance method, using IAwaitable and IAwaiter interfaces
First, similar to above ConfiguredTaskAwaitable<TResult>, a FuncAwaitable<TResult> can be implemented to wrap Func<TResult>:
internal struct FuncAwaitable<TResult> : IAwaitable<TResult> { private readonly Func<TResult> function;</span><span style="color: blue;">public </span><span style="color: black;">FuncAwaitable(</span><span style="color: #2b91af;">Func</span><span style="color: black;"><TResult> function) { </span><span style="color: blue;">this</span><span style="color: black;">.function = function; } </span><span style="color: blue;">public </span><span style="color: #2b91af;">IAwaiter</span><span style="color: black;"><TResult> GetAwaiter() { </span><span style="color: blue;">return new </span><span style="color: #2b91af;">FuncAwaiter</span><span style="color: black;"><TResult>(</span><span style="color: blue;">this</span><span style="color: black;">.function); }
}
FuncAwaitable<TResult> wrapper is used to implement IAwaitable<TResult>, so it has one instance method, GetAwaiter(), which returns a IAwaiter<TResult>, which wraps that Func<TResult> too. FuncAwaiter<TResult> is used to implement IAwaiter<TResult>:
public struct FuncAwaiter<TResult> : IAwaiter<TResult> { private readonly Task<TResult> task;</span><span style="color: blue;">public </span><span style="color: black;">FuncAwaiter(</span><span style="color: #2b91af;">Func</span><span style="color: black;"><TResult> function) { </span><span style="color: blue;">this</span><span style="color: black;">.task = </span><span style="color: blue;">new </span><span style="color: #2b91af;">Task</span><span style="color: black;"><TResult>(function); </span><span style="color: blue;">this</span><span style="color: black;">.task.Start(); } </span><span style="color: blue;">bool </span><span style="color: #2b91af;">IAwaiter</span><span style="color: black;"><TResult>.IsCompleted { </span><span style="color: blue;">get </span><span style="color: black;">{ </span><span style="color: blue;">return this</span><span style="color: black;">.task.IsCompleted; } } TResult </span><span style="color: #2b91af;">IAwaiter</span><span style="color: black;"><TResult>.GetResult() { </span><span style="color: blue;">return this</span><span style="color: black;">.task.Result; } </span><span style="color: blue;">void </span><span style="color: #2b91af;">INotifyCompletion</span><span style="color: black;">.OnCompleted(</span><span style="color: #2b91af;">Action </span><span style="color: black;">continuation) { </span><span style="color: blue;">new </span><span style="color: #2b91af;">Task</span><span style="color: black;">(continuation).Start(); }
}
Now a function can be awaited in this way:
int result = await new FuncAwaitable<int>(() => 0);
GetAwaiter() extension method, without IAwaitable interfaces
As IAwaitable shows, all that an awaitable needs is just a GetAwaiter() method. In above code, FuncAwaitable<TResult> is created as a wrapper of Func<TResult> and implements IAwaitable<TResult>, so that there is a GetAwaiter() instance method. If a GetAwaiter() extension method can be defined for Func<TResult>, then FuncAwaitable<TResult> is no longer needed:
public static class FuncExtensions { public static IAwaiter<TResult> GetAwaiter<TResult>(this Func<TResult> function) { return new FuncAwaiter<TResult>(function); } }
So a Func<TResult> function can be directly awaited:
int result = await new Func<int>(() => 0);
Use the built-in awaitable and awaiter: Task and TaskAwaiter
Remember the most frequently used awaitable / awaiter - Task / TaskAwaiter. With Task / TaskAwaiter, FuncAwaitable / FuncAwaiter are no longer needed:
public static class FuncExtensions { public static TaskAwaiter<TResult> GetAwaiter<TResult>(this Func<TResult> function) { Task<TResult> task = new Task<TResult>(function); task.Start(); return task.GetAwaiter(); // Returns a TaskAwaiter<TResult>. } }
Similarly, with this extension method:
public static class ActionExtensions { public static TaskAwaiter GetAwaiter(this Action action) { Task task = new Task(action); task.Start(); return task.GetAwaiter(); // Returns a TaskAwaiter. } }
an action can be awaited as well:
await new Action(() => { });
Now any function / action can be awaited:
await new Action(() => HelperMethods.IO()); // or: await new Action(HelperMethods.IO);
If function / action has parameter(s), closure can be used:
int arg0 = 0; int arg1 = 1; int result = await new Action(() => HelperMethods.IO(arg0, arg1));
Use Task.Run()
The above code is used to demonstrate how awaitable / awaiter can be implemented. As it is common scenario to await a function / action, .NET provides a built-in API: Task.Run(). Their implementations are similar to:
public class Task { public static Task Run(Action action) { // The implementation is similar to: Task task = new Task(action); task.Start(); return task; }</span><span style="color: blue;">public static </span><span style="color: #2b91af;">Task</span><span style="color: black;"><TResult> Run<TResult>(</span><span style="color: #2b91af;">Func</span><span style="color: black;"><TResult> function) { </span><span style="color: green;">// The implementation is similar to: </span><span style="color: #2b91af;">Task</span><span style="color: black;"><TResult> task = </span><span style="color: blue;">new </span><span style="color: #2b91af;">Task</span><span style="color: black;"><TResult>(function); task.Start(); </span><span style="color: blue;">return </span><span style="color: black;">task; }
}
In reality, this is how to await a function:
int result = await Task.Run(() => HelperMethods.IO(arg0, arg1));
and await a action:
await Task.Run(HelperMethods.IO);
Await IObservable<T>
IObservable<T> and IConnectableObservable<T> become awaitable too, if a reference is added for System.Reactive.Linq.dll, a part of Rx (Reactive Extensions). In this library, the GetAwaiter() extension methods are provided:
public static class Observable { public static AsyncSubject<TSource> GetAwaiter<TSource>(this IObservable<TSource> source);</span><span style="background: #f5f5f5; color: blue;">public static </span><span style="background: #f5f5f5; color: #2b91af;">AsyncSubject</span><span style="background: #f5f5f5; color: black;"><TSource> GetAwaiter<TSource>(</span><span style="background: #f5f5f5; color: blue;">this </span><span style="background: #f5f5f5; color: #2b91af;">IConnectableObservable</span><span style="background: #f5f5f5; color: black;"><TSource> source);
}
Each method returns a AsyncSubject<T>, which is an awaiter:
public sealed class AsyncSubject<T> : INotifyCompletion, ISubject<T>, ISubject<T, T>, IObserver<T>, IObservable<T>, IDisposable { public bool IsCompleted { get; } public void OnCompleted();}</span><span style="background: #f5f5f5; color: green;">// ...
So that can be used with the await keyword. Take IObservable<T> as example:
private static async Task AwaitObservable1() { IObservable<int> observable = Observable.Range(0, 3).Do(Console.WriteLine); await observable; }
This outputs:
0
1
2
Another example:
private static async Task<string> AwaitObservable2() { IObservable<string> observable = new string[] { "https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-1-compilation", "https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-2-awaitable-awaiter-pattern", "https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-3-runtime-context", } .ToObservable<string>() .SelectMany(async url => await new WebClient().DownloadStringTaskAsync(url)) .Select(StringExtensions.GetTitleFromHtml) .Do(Console.WriteLine);</span><span style="background: #f5f5f5; color: blue;">return await </span><span style="background: #f5f5f5; color: black;">observable;
}
where the GetTitleFromHtml is:
public static string GetTitleFromHtml(this string html) { Match match = new Regex( @".*<head>.*<title>(.*)</title>.*</head>.*", RegexOptions.IgnoreCase | RegexOptions.Singleline).Match(html); return match.Success ? match.Groups[1].Value : null; }
Executing above AwaitObservable2 method will output the title of each page:
Dixin's Blog - Understanding C# async / await (1) Compilation
Dixin's Blog - Understanding C# async / await (3) Runtime Context
Dixin's Blog - Understanding C# async / await (2) The Awaitable-Awaiter Pattern
which is exactly what’s between <tile> and </title>.