C#异步编程由浅入深(二)之Async/Await的使用
??考虑到直接讲实现一个类Task库思维有点跳跃,所以本节主要讲解Async/Await的本质作用(解决了什么问题),以及Async/Await的工作原理。实现一个类Task的库则放在后面讲。首先回顾一下上篇博客的场景。
class Program{public static string GetMessage(){return Console.ReadLine(); }public staticstring TranslateMessage(string msg)return msg; public staticvoid DispatherMessage(string msg)switch (msg){case "MOUSE_MOVE":{OnMOUSE_MOVE(msg); break; }case "MOUSE_DOWN":OnMouse_DOWN(msg); default:break; }public static void OnMOUSE_MOVE(string msg)Console.WriteLine("开始绘制鼠标形状"); public static int Http()Thread.Sleep(1000); //模拟网络IO延时return 1; public static void HttpAsync(Actionaction,Action error)//这里我们用另一个线程来实现异步IO,由于Http方法内部是通过Sleep来模拟网络IO延时的,这里也只能通过另一个线程来实现异步IO//但记住,多线程是实现异步IO的一个手段而已,它不是必须的,后面会讲到如何通过一个线程来实现异步IO。Thread thread = new Thread(() => try{int res = Http(); action(res); }catcherror(); }); thread.Start(); public static Task HttpAsync()return Task.Run(() => return Http(); public static void OnMouse_DOWN(string msg)HttpAsync().ContinueWith(t => if(t.Status == TaskStatus.Faulted)}else if(t.Status == TaskStatus.RanToCompletion)Console.WriteLine(1); //做一些工作})if (t.Status == TaskStatus.Faulted)else if (t.Status == TaskStatus.RanToCompletion)Console.WriteLine(2); Console.WriteLine(3); }); static void Main(string[] args)while (true)string msg = GetMessage(); if (msg == "quit") return; string m = TranslateMessage(msg); DispatherMessage(m); }
??在OnMouse_DOWN这个处理函数中,我们使用Task的ContinueWith函数进行链式操作,解决了回调地狱问题,但是总感觉有点那么不爽,我们假想有个关键字await它能实现以下作用:首先await必须是Task类型,必须是Task类型的(其实不是必要条件,后面会讲到)原因是保证必须有ContinueWith这个函数,如果Task没有返回值,则把await后面的代码放到Task中的ContinueWith函数体内,如果有返回值,则把Await后的结果转化为访问Task.Result属性,文字说的可能不明白,看下示例代码
//无返回值转换前public async void Example(){Task t = Task.Run(() =>{Thread.Sleep(1000); }); await t; //做一些工作}//无返回值转换后public void Example()t.ContinueWith(task => //做一些工作//有返回值转换前Taskt = Task.Run (() =>return 1; int res = await t; //使用res做一些工作//有返回值转换后//使用task.Result做一些工作
??看起来不错,但至少有以下问题,如下:
- 该种转换方法不能很好的转换Try/Catch结构
- 在循环结构中使用await不好转换
- 该实现与Task类型紧密联系
public static Task WorkAsync(){return Task.Run(() => {Thread.Sleep(1000); Console.WriteLine("Done!"); }); }public static async void Test(){Console.WriteLine("步骤1"); await WorkAsync(); Console.WriteLine("步骤2"); await WorkAsync(); Console.WriteLine("步骤3"); }
??手动写一个简单的状态机类
public class TestAsyncStateMachine{public int _state = 0; public void Start() => MoveNext(); public void MoveNext(){switch(_state){case 0:{goto Step0; }case 1:goto Step1; default:Console.WriteLine("步骤3"); return; }Step0:Console.WriteLine("步骤1"); _state = 1; WorkAsync().ContinueWith(t => this.MoveNext()); return; Step1:_state = -1; Console.WriteLine("步骤2"); }}
??而Test()方法则变成了这样
public static void Test(){new TestAsyncStateMachine().Start(); }
??注意Test()方法返回的是void,这意味这调用方将不能await Test()。如果返回Task,这个状态机类是不能正确处理的,如果要正确处理,那么状态机在Start()启动后,必须返回一个Task,而这个Task在整个状态机流转完毕后要变成完成状态,以便调用方在该Task上调用的ContinueWith得以继续执行,而就Task这个类而言,它是没有提供这种方法(内部有,但没有对外暴露)来主动控制Task的状态的,这个与JS中的Promise不同,JS里面用Reslove函数来主动控制Promise的状态,并导致在该Promise上面的Then链式调用得以继续完成,而在C#里面怎么做呢?既然使用了状态机来实现async/await,那么在转换一个返回Task的函数时肯定会遇到,怎么处理?后面讲。
??首先解决一下与Task类型紧密联系这个问题。
??从状态机中可以看到,主要使用到了Task中的ContinueWith这个函数,它的语义是在任务完成后,执行回调函数,通过回调函数拿到结果,这个编程风格也叫做CPS(Continuation-Passing-Style, 续体传递风格),那么我们能不能把这个函数给抽象出来呢?语言开发者当然想到了,它被抽象成了一个Awaiter因此编译器要求await的类型必须要有GetAwaiter方法,什么样的类型才是Awaiter呢?编译器规定主要实现了如下几个方法的类型就是Awaiter:
- 必须继承INotifyCompletion接口,并实现其中的OnCompleted(Action continuation)方法
- 必须包含IsCompleted属性
- 必须包含GetResult()方法
public class TestAsyncStateMachine{public int _state = 0; public void Start() => MoveNext(); public void MoveNext(){switch(_state){case 0:{goto Step0; }case 1:goto Step1; default:Console.WriteLine("步骤3"); return; }Step0:Console.WriteLine("步骤1"); _state = 1; TaskAwaiter taskAwaiter; taskAwaiter = WorkAsync().GetAwaiter(); if (taskAwaiter.IsCompleted) goto Step1; taskAwaiter.OnCompleted(() => this.MoveNext()); return; Step1:_state = -1; Console.WriteLine("步骤2"); if (taskAwaiter.IsCompleted) MoveNext(); }}
??可以看到去掉了与Task中ContinueWith的耦合关系,并且如果任务已经完成,则可以直接执行下个任务,避免了无用的开销。
??因此我们可以总结一下async/await:
- async/await只是表示这个方法需要编译器进行特殊处理,并不代表它本身一定是异步的。
- Task类中的GetAwaiter主要是给编译器用的。
//该类型包含GetAwaiter方法,且GetAwaiter()返回的类型包含三个必要条件public class MyAwaiter : INotifyCompletion{public void OnCompleted(Action continuation){continuation(); }public bool IsCompleted { get; }public void GetResult()public MyAwaiter GetAwaiter() => new MyAwaiter(); }
【C#异步编程由浅入深(二)之Async/Await的使用】??一个测试函数,注意必须返回void
public static async void AwaiterTest(){await new MyAwaiter(); Console.WriteLine("Done"); }
??可以看到这是完全同步进行的。
到此这篇关于C#异步编程由浅入深(二)之Async/Await的作用.的文章就介绍到这了,更多相关C#异步编程Async/Await内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
推荐阅读
- 笔试题|字节跳动2021春招研发第二场笔试编程题(三)
- 笔试题|字节跳动2021春招研发第二场笔试编程题(二)
- 算法|字节跳动2019春招研发编程题
- java|Java——字节跳动2019春招研发部分编程题(一)
- CMP5327 游戏编程
- Spring5新特性之Reactive响应式编程
- Android系统编程入门系列之硬件交互——无线通信WLAN
- ECS 34 编程指南
- C语言编程程序的内存如何布局
- 四、回调函数