Understanding C# async / await 第2章

Understanding C# async / await

Understanding C# async / await 第1章原文地址 Understanding C# async / await 第2章原文地址 Understanding C# async / await 第3章原文地址

2 The Awaitable-Awaiter Pattern

2.1 What is awaitable

第1部分显示了所有Task类都是awaitable的。实际上,还有其他awaitable类型。如下所示:

 1Task<int> task = new Task<int>(() => 0);
 2int result = await task.ConfigureAwait(false); // Returns a ConfiguredTaskAwaitable<TResult>.
 3The returned ConfiguredTaskAwaitable<TResult> struct is awaitable. And it is not Task at all:
 4
 5public struct ConfiguredTaskAwaitable<TResult>
 6{
 7    private readonly ConfiguredTaskAwaiter m_configuredTaskAwaiter;
 8
 9    internal ConfiguredTaskAwaitable(Task<TResult> task, bool continueOnCapturedContext)
10    {
11        this.m_configuredTaskAwaiter = new ConfiguredTaskAwaiter(task, continueOnCapturedContext);
12    }
13
14    public ConfiguredTaskAwaiter GetAwaiter()
15    {
16        return this.m_configuredTaskAwaiter;
17    }
18}
19

ConfiguredTaskAwaitable<TResult> 类有GetAwaiter() 方法,同样地在Task类中也有GetAwaiter() 方法

 1public class Task
 2{
 3    public TaskAwaiter GetAwaiter()
 4    {
 5        return new TaskAwaiter(this);
 6    }
 7}
 8
 9public class Task<TResult> : Task
10{
11    public new TaskAwaiter<TResult> GetAwaiter()
12    {
13        return new TaskAwaiter<TResult>(this);
14    }
15}

Task类的Yield() 方法则是另一个示例:

 1await Task.Yield(); // Returns a YieldAwaitable.
 2The returned YieldAwaitable is not Task either:
 3
 4public struct YieldAwaitable
 5{
 6    public YieldAwaiter GetAwaiter()
 7    {
 8        return default(YieldAwaiter);
 9    }
10}

同样,它也有GetAwaiter() 方法。本文将探讨什么是可等待的。

2.2 The awaitable-awaiter pattern

通过观察不同的awaitable / awaiter类型,我们可以知道一个对象满足以下条件,则是awaitable的

  • 它具有GetAwaiter() 方法(可是实例方法或或者扩展方法);
  • 它的GetAwaiter() 方法返回一个awaiter。如果满足以下条件,则GetAwaiter() 方法返回的object是一个awaiter:
    • 它实现INotifyCompletionICriticalNotifyCompletion接口;
    • 它有一个IsCompleted属性,该属性必须有一个getter,且该getter返回bool值。
    • 它具有GetResult() 方法,该方法返回void或结果。

这种awaitable-awaiter模式和iteratable-iterator模式非常相似。这是iteratable-iterator的接口定义如下:

 1public interface IEnumerable
 2{
 3    IEnumerator GetEnumerator();
 4}
 5
 6public interface IEnumerator
 7{
 8    object Current { get; }
 9    bool MoveNext();
10    void Reset();
11}
12
13public interface IEnumerable<out T> : IEnumerable
14{
15    IEnumerator<T> GetEnumerator();
16}
17
18public interface IEnumerator<out T> : IDisposable, IEnumerator
19{
20    T Current { get; }
21}

2.3 The “missing” IAwaitable / IAwaiter interfaces

IEnumerableIEnumerator接口类似,也可以通过IAwaitable / IAwaiter接口可视化awaitable / awaiter对象。下面代码是非泛型版本。

 1public interface IAwaitable
 2{
 3    IAwaiter GetAwaiter();
 4}
 5
 6public interface IAwaiter : INotifyCompletion // or ICriticalNotifyCompletion
 7{
 8    // INotifyCompletion has one method: void OnCompleted(Action continuation);
 9
10    // ICriticalNotifyCompletion implements INotifyCompletion,
11    // also has this method: void UnsafeOnCompleted(Action continuation);
12
13    bool IsCompleted { get; }
14
15    void GetResult();
16}

请注意,非泛型版本的GetResult() 方法在此返回void。Task.GetAwaiter() / TaskAwaiter.GetResult() 方法就是这种情况。下面的代码则是泛型版本的定义

 1public interface IAwaitable<out TResult>
 2{
 3    IAwaiter<TResult> GetAwaiter();
 4}
 5
 6public interface IAwaiter<out TResult> : INotifyCompletion // or ICriticalNotifyCompletion
 7{
 8    bool IsCompleted { get; }
 9
10    TResult GetResult();
11}

和非泛型的相比,泛型版本的GetResult() 方法在此返回结果值。Task<Result>.GetAwaiter() / TaskAwaiter<Result>..GetResult() 方法就是这种情况。

请注意,.NET core没有定义这些IAwaitable / IAwaiter接口。IAwaitable接口将GetAwaiter() 方法约束为实例方法。实际上,C#同时支持GetAwaiter() 实例方法和GetAwaiter() 扩展方法。

在这里,这些接口仅用于更好地让awaitable/awaiter对象可见。现在,如果再次查看上面的ConfiguredTaskAwaitable / ConfiguredTaskAwaiterYieldAwaitable / YieldAwaiterTask / TaskAwaiter这些接口,它们都“隐式”实现了这些“缺失的IAwaitable / IAwaiter接口。本文的其余部分将展示如何实现awaitable / awaiter

2.4 Await any function / action

await不能作用在一个lambda上,如下代码会引发“Cannot await ‘lambda expression’”的编译错误

1int result = await (() => 0);

因为一个lambda可能是一个函数,也可能是一个表达式,存在着歧义,所以会导致编译错误,如果要用lambda,则需要将其赋值到一个Func对象上,如下代码:

1int result = await new Func<int>(() => 0);

这时候消除了歧义了,但又不符合await的使用规范,这时候又会出现一个编译错误“Cannot await ‘System.Func<int>'”。所以必须引入awaitable/awaiter的定义模式来实现她。如果一个对象是awaitable的,则首先要定义一个**GetAwaiter()**方法,如下代码:

 1internal struct FuncAwaitable<TResult> : IAwaitable<TResult>
 2{
 3    private readonly Func<TResult> function;
 4
 5    public FuncAwaitable(Func<TResult> function)
 6    {
 7        this.function = function;
 8    }
 9   
10    /*  FuncAwaitable<TResult>要成为awaitable的话,其必须返回一个GetAwaiter方法,且该方法返回的对象
11	    必须满足以下条件:
12	    它实现INotifyCompletion或ICriticalNotifyCompletion接口;
13        它有一个IsCompleted**属性,该属性必须有一个getter,且该getter返回bool值。
14        它具有GetResult()方法,该方法返回void或结果。
15	*/
16    public IAwaiter<TResult> GetAwaiter()
17    {
18        return new FuncAwaiter<TResult>(this.function);
19    }
20}

FuncAwaitable<TResult> 类继承实现自IAwaitable<TResult>接口,所以这时候它满足“它具有GetAwaiter()方法(可以是实例方法或者扩展方法)” 这一个条件。这个GetAwaiter() 方法返回一个继承实现自 IAwaiter<TResult> 接口的FuncAwaiter<TResult> 类,该类的定义如下:

 1public struct FuncAwaiter<TResult> : IAwaiter<TResult>
 2{
 3    private readonly Task<TResult> task;
 4
 5    public FuncAwaiter(Func<TResult> function)
 6    {
 7        this.task = new Task<TResult>(function);
 8        this.task.Start();
 9    }
10
11    bool IAwaiter<TResult>.IsCompleted
12    {
13        get
14        {
15            return this.task.IsCompleted;
16        }
17    }
18
19    TResult IAwaiter<TResult>.GetResult()
20    {
21        return this.task.Result;
22    }
23
24    void INotifyCompletion.OnCompleted(Action continuation)
25    {
26        new Task(continuation).Start();
27    }
28}

从上面的代码可以看到FuncAwaiter<TResult> 类的定义实现满足了:

  • GetAwaiter() 方法返回的object是一个awaiter:
    • 它实现INotifyCompletionICriticalNotifyCompletion接口;
    • 它有一个IsCompleted属性,该属性必须有一个getter,且该getter返回bool值。
    • 它具有GetResult() 方法,该方法返回void或结果。

这三个条件,所以这时候这个FuncAwaitable<TResult> 类是awaiable了,可以用以下方法执行await操作,如下:

1int result = await new FuncAwaitable<int>(() => 0);

2.5 GetAwaiter() extension method, without IAwaitable interfaces

IAwaitable接口所示,所有需要满足的只是一个GetAwaiter() 方法。在上面的代码中,创建了FuncAwaitable<TResult>作为Func <TResult>的包装,并实现了IAwaitable<TResult>,因此有一个GetAwaiter()实例方法。如果可以为Func <TResult>定义GetAwaiter() 扩展方法,则不再需要FuncAwaitable<TResult> 类,如下代码所示:

1public static class FuncExtensions
2{
3    public static IAwaiter<TResult> GetAwaiter<TResult>(this Func<TResult> function)
4    {
5        return new FuncAwaiter<TResult>(function);
6    }
7}

因此可以直接等待Func <TResult> 类进行await操作:

1int result = await new Func<int>(() => 0);

2.6 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: 系统提供了内置的基于awaitable/awaiter模式的类,他们是Task / TaskAwaiter类,使用方式如下代码:

1public static class FuncExtensions
2{
3    public static TaskAwaiter<TResult> GetAwaiter<TResult>(this Func<TResult> function)
4    {
5        Task<TResult> task = new Task<TResult>(function);
6        task.Start();
7        return task.GetAwaiter(); // Returns a TaskAwaiter<TResult>.
8    }
9}

类似地如果用扩展方法来定义的话如下:

1public static class ActionExtensions
2{
3    public static TaskAwaiter GetAwaiter(this Action action)
4    {
5        Task task = new Task(action);
6        task.Start();
7        return task.GetAwaiter(); // Returns a TaskAwaiter.
8    }
9}

一個Action類對象可以如下使用awaitan action can be awaited as well:

1await new Action(() => { });

现在所有的方法都可以使用await了

1await new Action(() => HelperMethods.IO()); // or: await new Action(HelperMethods.IO);

如果函数还有参数,可以使用闭包。

1int arg0 = 0;
2int arg1 = 1;
3int result = await new Action(() => HelperMethods.IO(arg0, arg1));

2.7 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: 上面的代码用于演示如何实现waiting / awaiter。由于等待功能/动作是常见的情况,.NET提供了内置的API:Task.Run()。它们的实现类似于:

 1public class Task
 2{
 3    public static Task Run(Action action)
 4    {
 5        // The implementation is similar to:
 6        Task task = new Task(action);
 7        task.Start();
 8        return task;
 9    }
10
11    public static Task<TResult> Run<TResult>(Func<TResult> function)
12    {
13        // The implementation is similar to:
14        Task<TResult> task = new Task<TResult>(function);
15        task.Start();
16        return task;
17    }
18}

实际上这等待一个Task完成的方法如下:

1int result = await Task.Run(() => HelperMethods.IO(arg0, arg1));

2.8 Await IObservable

如果为Rx(反应性扩展)的一部分System.Reactive.Linq.dll添加了引用,则IObservable<T>IConnectableObservable<T> 也将变为等待状态。在此库中,提供了GetAwaiter() 扩展方法:

1public static class Observable
2{
3    public static AsyncSubject<TSource> GetAwaiter<TSource>(this IObservable<TSource> source);
4    public static AsyncSubject<TSource> GetAwaiter<TSource>(this IConnectableObservable<TSource> source);
5}

每个方法都返回一个AsyncSubject<T> 对象,它是一个awaiter:

1public sealed class AsyncSubject<T> : INotifyCompletion, ISubject<T>, ISubject<T, T>, IObserver<T>, IObservable<T>, IDisposable
2{
3    public bool IsCompleted { get; }
4    public void OnCompleted();
5    // ...
6}

这样可以与await关键字一起使用。以IObservable<T> 为例:

1private static async Task AwaitObservable1()
2{
3    IObservable<int> observable = Observable.Range(0, 3).Do(Console.WriteLine);
4    await observable;
5}

输出数值如下:

0 1 2

另一个例子如下:

 1private static async Task<string> AwaitObservable2()
 2{
 3    IObservable<string> observable = new string[]
 4        {
 5            "https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-1-compilation",
 6            "https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-2-awaitable-awaiter-pattern",
 7            "https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-3-runtime-context",
 8        }
 9        .ToObservable<string>()
10        .SelectMany(async url => await new WebClient().DownloadStringTaskAsync(url))
11        .Select(StringExtensions.GetTitleFromHtml)
12        .Do(Console.WriteLine);
13
14    return await observable;
15}

GetTitleFromHtml方法如下:

1public static string GetTitleFromHtml(this string html)
2{
3    Match match = new Regex(
4        @".*<head>.*<title>(.*)</title>.*</head>.*",
5        RegexOptions.IgnoreCase | RegexOptions.Singleline).Match(html);
6    return match.Success ? match.Groups[1].Value : null;
7}

执行上面的AwaitObservable2方法将输出每个页面的标题:

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

正是<tile>和</title>之间的内容。