Understanding C# async / await 第2章
Table of Contents
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类型。如下所示:
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;
internal ConfiguredTaskAwaitable(Task<TResult> task, bool continueOnCapturedContext)
{
this.m_configuredTaskAwaiter = new ConfiguredTaskAwaiter(task, continueOnCapturedContext);
}
public ConfiguredTaskAwaiter GetAwaiter()
{
return this.m_configuredTaskAwaiter;
}
}
ConfiguredTaskAwaitable<TResult> 类有GetAwaiter() 方法,同样地在Task类中也有GetAwaiter() 方法
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() 方法则是另一个示例:
await Task.Yield(); // Returns a YieldAwaitable.
The returned YieldAwaitable is not Task either:
public struct YieldAwaitable
{
public YieldAwaiter GetAwaiter()
{
return default(YieldAwaiter);
}
}
同样,它也有GetAwaiter() 方法。本文将探讨什么是可等待的。
2.2 The awaitable-awaiter pattern
通过观察不同的awaitable / awaiter类型,我们可以知道一个对象满足以下条件,则是awaitable的
- 它具有GetAwaiter() 方法(可是实例方法或或者扩展方法);
- 它的GetAwaiter() 方法返回一个awaiter。如果满足以下条件,则GetAwaiter() 方法返回的object是一个awaiter:
- 它实现INotifyCompletion或ICriticalNotifyCompletion接口;
- 它有一个IsCompleted属性,该属性必须有一个getter,且该getter返回bool值。
- 它具有GetResult() 方法,该方法返回void或结果。
这种awaitable-awaiter模式和iteratable-iterator模式非常相似。这是iteratable-iterator的接口定义如下:
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> : IDisposable, IEnumerator
{
T Current { get; }
}
2.3 The “missing” IAwaitable / IAwaiter interfaces
与IEnumerable和IEnumerator接口类似,也可以通过IAwaitable / IAwaiter接口可视化awaitable / awaiter对象。下面代码是非泛型版本。
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);
bool IsCompleted { get; }
void GetResult();
}
请注意,非泛型版本的GetResult() 方法在此返回void。Task.GetAwaiter() / TaskAwaiter.GetResult() 方法就是这种情况。下面的代码则是泛型版本的定义
public interface IAwaitable<out TResult>
{
IAwaiter<TResult> GetAwaiter();
}
public interface IAwaiter<out TResult> : INotifyCompletion // or ICriticalNotifyCompletion
{
bool IsCompleted { get; }
TResult GetResult();
}
和非泛型的相比,泛型版本的GetResult() 方法在此返回结果值。Task<Result>.GetAwaiter() / TaskAwaiter<Result>..GetResult() 方法就是这种情况。
请注意,.NET core没有定义这些IAwaitable / IAwaiter接口。IAwaitable接口将GetAwaiter() 方法约束为实例方法。实际上,C#同时支持GetAwaiter() 实例方法和GetAwaiter() 扩展方法。
在这里,这些接口仅用于更好地让awaitable/awaiter对象可见。现在,如果再次查看上面的ConfiguredTaskAwaitable / ConfiguredTaskAwaiter,YieldAwaitable / YieldAwaiter,Task / TaskAwaiter这些接口,它们都“隐式”实现了这些“缺失的 ” IAwaitable / IAwaiter接口。本文的其余部分将展示如何实现awaitable / awaiter。
2.4 Await any function / action
await不能作用在一个lambda上,如下代码会引发“Cannot await ’lambda expression’”的编译错误
int result = await (() => 0);
因为一个lambda可能是一个函数,也可能是一个表达式,存在着歧义,所以会导致编译错误,如果要用lambda,则需要将其赋值到一个Func对象上,如下代码:
int result = await new Func<int>(() => 0);
这时候消除了歧义了,但又不符合await的使用规范,这时候又会出现一个编译错误“Cannot await ‘System.Func<int>’”。所以必须引入awaitable/awaiter的定义模式来实现她。如果一个对象是awaitable的,则首先要定义一个**GetAwaiter()**方法,如下代码:
internal struct FuncAwaitable<TResult> : IAwaitable<TResult>
{
private readonly Func<TResult> function;
public FuncAwaitable(Func<TResult> function)
{
this.function = function;
}
/* FuncAwaitable<TResult>要成为awaitable的话,其必须返回一个GetAwaiter方法,且该方法返回的对象
必须满足以下条件:
它实现INotifyCompletion或ICriticalNotifyCompletion接口;
它有一个IsCompleted**属性,该属性必须有一个getter,且该getter返回bool值。
它具有GetResult()方法,该方法返回void或结果。
*/
public IAwaiter<TResult> GetAwaiter()
{
return new FuncAwaiter<TResult>(this.function);
}
}
FuncAwaitable<TResult> 类继承实现自IAwaitable<TResult>接口,所以这时候它满足“它具有GetAwaiter()方法(可以是实例方法或者扩展方法)” 这一个条件。这个GetAwaiter() 方法返回一个继承实现自 IAwaiter<TResult> 接口的FuncAwaiter<TResult> 类,该类的定义如下:
public struct FuncAwaiter<TResult> : IAwaiter<TResult>
{
private readonly Task<TResult> task;
public FuncAwaiter(Func<TResult> function)
{
this.task = new Task<TResult>(function);
this.task.Start();
}
bool IAwaiter<TResult>.IsCompleted
{
get
{
return this.task.IsCompleted;
}
}
TResult IAwaiter<TResult>.GetResult()
{
return this.task.Result;
}
void INotifyCompletion.OnCompleted(Action continuation)
{
new Task(continuation).Start();
}
}
从上面的代码可以看到FuncAwaiter<TResult> 类的定义实现满足了:
- GetAwaiter() 方法返回的object是一个awaiter:
- 它实现INotifyCompletion或ICriticalNotifyCompletion接口;
- 它有一个IsCompleted属性,该属性必须有一个getter,且该getter返回bool值。
- 它具有GetResult() 方法,该方法返回void或结果。
这三个条件,所以这时候这个FuncAwaitable<TResult> 类是awaiable了,可以用以下方法执行await操作,如下:
int 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> 类,如下代码所示:
public static class FuncExtensions
{
public static IAwaiter<TResult> GetAwaiter<TResult>(this Func<TResult> function)
{
return new FuncAwaiter<TResult>(function);
}
}
因此可以直接等待Func <TResult> 类进行await操作:
int 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类,使用方式如下代码:
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>.
}
}
类似地如果用扩展方法来定义的话如下:
public static class ActionExtensions
{
public static TaskAwaiter GetAwaiter(this Action action)
{
Task task = new Task(action);
task.Start();
return task.GetAwaiter(); // Returns a TaskAwaiter.
}
}
一個Action類對象可以如下使用awaitan action can be awaited as well:
await new Action(() => { });
现在所有的方法都可以使用await了
await new Action(() => HelperMethods.IO()); // or: await new Action(HelperMethods.IO);
如果函数还有参数,可以使用闭包。
int arg0 = 0;
int arg1 = 1;
int 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()。它们的实现类似于:
public class Task
{
public static Task Run(Action action)
{
// The implementation is similar to:
Task task = new Task(action);
task.Start();
return task;
}
public static Task<TResult> Run<TResult>(Func<TResult> function)
{
// The implementation is similar to:
Task<TResult> task = new Task<TResult>(function);
task.Start();
return task;
}
}
实际上这等待一个Task完成的方法如下:
int result = await Task.Run(() => HelperMethods.IO(arg0, arg1));
2.8 Await IObservable
如果为Rx(反应性扩展)的一部分System.Reactive.Linq.dll添加了引用,则IObservable<T> 和IConnectableObservable<T> 也将变为等待状态。在此库中,提供了GetAwaiter() 扩展方法:
public static class Observable
{
public static AsyncSubject<TSource> GetAwaiter<TSource>(this IObservable<TSource> source);
public static AsyncSubject<TSource> GetAwaiter<TSource>(this IConnectableObservable<TSource> source);
}
每个方法都返回一个AsyncSubject<T> 对象,它是一个awaiter:
public sealed class AsyncSubject<T> : INotifyCompletion, ISubject<T>, ISubject<T, T>, IObserver<T>, IObservable<T>, IDisposable
{
public bool IsCompleted { get; }
public void OnCompleted();
// ...
}
这样可以与await关键字一起使用。以IObservable<T> 为例:
private static async Task AwaitObservable1()
{
IObservable<int> observable = Observable.Range(0, 3).Do(Console.WriteLine);
await observable;
}
输出数值如下:
0 1 2
另一个例子如下:
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);
return await observable;
}
GetTitleFromHtml方法如下:
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;
}
执行上面的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>之间的内容。