










1、背景知识
(1)接口 INotifyCompletion

public interface INotifyCompletion { void OnCompleted(Action continuation); }
View Code
实现这个接口的实例(例如一个 Task 对象),在任务完成后,调用 OnCompleted 方法,以执行后续的委托(continuation)。
(2)关键字 await
关键字 await 是一个运算符,它的操作数是一个实现了 GetAwaiter 方法的实例。该方法返回一个 Awaiter。

internal partial class Program { //调用方线程 private static async void ToTest6() { await new Test6(); } } internal class Test6 { public MyAwaiter GetAwaiter() { return new MyAwaiter(); } }
View Code
2、Awaiter
要实现一个 Awaiter,必须实现 INotifyCompletion 接口、IsCompleted 属性和 GetResult 方法:
public class MyAwaiter : INotifyCompletion { public bool IsCompleted { get; set; } public void GetResult() { //throw new NotImplementedException(); } public void OnCompleted(Action continuation) { //throw new NotImplementedException(); } }
3、执行流程
(1)先实现一个方法,用于输出当前线程Id,以及调用时给的标记

internal partial class Test6 { public static void PrintThreadInfo(string callTag) { //输出当前线程Id,以及调用时给的标记 Console.WriteLine($" Thread Id:{Environment.CurrentManagedThreadId,2} - {callTag}"); } }
View Code
(2)测试代码
在 MyAwaier 中的重要位置都输出调用时给的标记:

public class MyAwaiter : INotifyCompletion { private bool _isCompleted = true; public bool IsCompleted { get { Test6.PrintThreadInfo($"Get IsCompleted:{_isCompleted}"); return _isCompleted; } set { Test6.PrintThreadInfo($"Set IsCompleted:{value}"); _isCompleted = value; } } public void GetResult() { Test6.PrintThreadInfo("Get Result"); } public void OnCompleted(Action continuation) { Test6.PrintThreadInfo("On Completed"); } }
View Code
实现一个可作为 await 运算符的操作数的类:

internal partial class Test6 { public MyAwaiter GetAwaiter() { PrintThreadInfo("Get a awaiter"); return new MyAwaiter(); } }
View Code
对它测试调用:

internal partial class Program { static void Main(string[] args) { Test6.PrintThreadInfo("Main"); Task.Run(() => { ToTest6(); }); Console.ReadKey(); } private static async void ToTest6() { Test6.PrintThreadInfo("before await"); await new Test6(); Test6.PrintThreadInfo("after await"); } }
View Code
运行结果:

结果分析:貌似一切正常,除了 OnCompleted 方法未被调用外。
A、将 _isCompleted 设置为 false,即:private bool _isCompleted = false;
运行结果:

结果分析:未见“Get Result”和“after await”,即:Awaiter 的 GetResult 方法 以及 调用方 await 之后的代码未被执行——线程被挂起了【异常】。
B、修改 INotifyCompletion 接口的实现,添加对参数 continuation 进行是否为 null 的判断,如果不为 null,则调用:

public void OnCompleted(Action continuation) { Test6.PrintThreadInfo("On Completed"); if (continuation != null) { Test6.PrintThreadInfo($"continuation is not null"); continuation.Invoke(); } }
View Code
运行结果:

结果分析:
C、再次修改 INotifyCompletion 接口的实现,执行任务之后,将 IsCompleted 属性设置为 true,并调用参数 continuation:

public void OnCompleted(Action continuation) { Test6.PrintThreadInfo("On Completed"); //if (continuation != null) //{ // Test6.PrintThreadInfo($"continuation is not null"); // continuation.Invoke(); //} Task.Run(() => { Test6.PrintThreadInfo("Task Run"); Thread.Sleep(1000); }).ContinueWith((a) => { IsCompleted = true; continuation.Invoke(); }); }
View Code
运行结果:

结果分析:Id 为 9 的线程果然被挂起,而由新线程 6 继续之后的代码,看起来一切正常。
(3)测试结论
根据上述测试代码,可以初步得出结论:
测试代码的执行顺序如下:
4、改进代码
目的:使 Test6 成为一个类似任务(Task-Like)的类。
(1)根据执行流程测试代码的结论,对测试代码做如下改进:

internal partial class Test6 : INotifyCompletion { private readonly Func<int> _func = null!; public Test6(Func<int> func) { _func = func; } public Test6 GetAwaiter() { PrintThreadInfo("Get a awaiter"); return this; } private bool _isCompleted = false; public bool IsCompleted { get { PrintThreadInfo($"Get IsCompleted:{_isCompleted}"); return _isCompleted; } set { PrintThreadInfo($"Set IsCompleted:{value}"); _isCompleted = value; } } private int _result = 0; public int GetResult() { PrintThreadInfo("Get Result"); return _result; } public void OnCompleted(Action continuation) { PrintThreadInfo("On Completed"); ThreadPool.QueueUserWorkItem(state => { PrintThreadInfo($"Task Run at {DateTime.Now}"); _result = _func.Invoke(); IsCompleted = true; continuation.Invoke(); }); } public static void PrintThreadInfo(string callTag) { //输出当前线程Id,以及调用时给的标记 Console.WriteLine($" Thread Id:{Environment.CurrentManagedThreadId,2} - {callTag}"); } }
View Code
调用并输出结果:

internal partial class Program { static void Main(string[] args) { Test6.PrintThreadInfo("Main"); Task.Run(() => { ToTest6(); }); Console.ReadKey(); } private static async void ToTest6() { Test6.PrintThreadInfo("before await"); int awaitResult = await new Test6(Work); Test6.PrintThreadInfo($"after await:{awaitResult}"); } private static int Work() { Thread.Sleep(2000); Test6.PrintThreadInfo($"Work finished at {DateTime.Now}"); return 1; } }
View Code
运行结果:

结果分析:Id 为 6 的线程被挂起,而由新线程 10 继续之后的代码,看起来一切正常。
(2)使用泛型改进,并可异步启动(具有 Start 方法):

public interface IAwaiter<out T> : INotifyCompletion { bool IsCompleted { get; } T GetResult(); } public interface IAwaitable<out T> { IAwaiter<T> GetAwaiter(); } internal class Test6<T> : IAwaitable<T>, IAwaiter<T> { private readonly Task<T> _task = null!; public Test6(Task<T> task) { _task = task; } public Test6(Func<T> func) { _task = new Task<T>(func); } public void Start() { _task.ContinueWith(t => { _isCompleted = true; _result = _task.Result; }); _task.Start(); } public IAwaiter<T> GetAwaiter() { TestHelper.PrintThreadInfo("Get a awaiter"); return this; } private bool _isCompleted = false; public bool IsCompleted { get { TestHelper.PrintThreadInfo($"Get IsCompleted:{_isCompleted}"); return _isCompleted; } set { TestHelper.PrintThreadInfo($"Set IsCompleted:{value}"); _isCompleted = value; } } private T _result = default!; public T GetResult() { TestHelper.PrintThreadInfo("Get Result"); return _result; } public void OnCompleted(Action continuation) { TestHelper.PrintThreadInfo("On Completed"); if (!_isCompleted) { if (_task.Status == TaskStatus.Created) { Start(); } _task.Wait(); } continuation.Invoke(); } }
View Code
注:将 PrintThreadInfo 方法移到了 TestHelper 类中。
调用并输出结果:

internal partial class Program { static void Main(string[] args) { TestHelper.PrintThreadInfo("Main"); Task.Run(() => { ToTest6(); }); Console.ReadKey(); } //调用方线程 private static async void ToTest6() { var test1 = new Test6<string>(Work1); var test2 = new Test6<int>(Work2); test1.Start(); //test2.Start(); TestHelper.PrintThreadInfo("before await1"); string awaitResult1 = await test1; TestHelper.PrintThreadInfo($"after await1:{awaitResult1}"); TestHelper.PrintThreadInfo("before await2"); int awaitResult2 = await test2; TestHelper.PrintThreadInfo($"after await2:{awaitResult2}"); } private static string Work1() { TestHelper.PrintThreadInfo($"Work1 started at {DateTime.Now}"); Thread.Sleep(2000); return $"Work finished at {DateTime.Now}"; } private static int Work2() { TestHelper.PrintThreadInfo($"Work2 started at {DateTime.Now}"); return 1; } } public static class TestHelper { public static void PrintThreadInfo(string callTag) { //输出当前线程Id,以及调用时给的标记 Console.WriteLine($" Thread Id:{Environment.CurrentManagedThreadId,2} - {callTag}"); } }
View Code
运行结果:

结果分析:
至此,一个简单的具有泛型能力的类似任务(Task-Like)的类 Test6 实现完成。
5、总结
(1)要实现一个 Awaiter,必须实现 INotifyCompletion 接口、IsCompleted 属性和 GetResult 方法。
(2)关键字 await 是一个运算符,它的操作数是一个实现了 GetAwaiter 方法的实例。
(3)Awaiter 的 GetResult 方法 以及 调用方 await 之后(下一个 await 之前)的代码,被自动塞进了一个委托中(continuation)。
(4)Awaiter 的 IsCompleted 属性为 true 时,“GetResult 方法 以及 调用方 await 之后(下一个 await 之前)的代码” 会立即被执行,并不执行 OnCompleted 方法;
Awaiter 的 IsCompleted 属性为 false 时,立即执行 OnCompleted 方法,并且通过委托(continuation)的调用,来执行“GetResult 方法 以及 调用方 await 之后(下一个 await 之前)的代码”;
如果不调用委托(continuation)将导致线程被挂起,程序无法进行下去,而成为一个 BUG。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。