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:
    • 它实现INotifyCompletionICriticalNotifyCompletion接口;
    • 它有一个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

IEnumerableIEnumerator接口类似,也可以通过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 / ConfiguredTaskAwaiterYieldAwaitable / YieldAwaiterTask / 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:
    • 它实现INotifyCompletionICriticalNotifyCompletion接口;
    • 它有一个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>之间的内容。

kumakoko avatar
kumakoko
pure coder
comments powered by Disqus