关于C# await的一点新理解
关于await又理解深一点了,以前有点懵,
原来await 是对Task.Run的一个修饰(更准确的说是对Task实例的一个修饰,因为Task实例是必然会包装一个委托的所以就实现了对方法的包装,当这个方法调用时也就是Run时你可以选择是否使用await),叶节点,后续技节点是对标有async的方法进行串烧修饰,所以根在Task.Run这个方法要所以要理解await必须要详细查看Task.Run
Task.Run是调用后直接把Run的参数委托丢线程池的然后不等待直接返回的,所以返回是一个Task/Task<T>只有这两种情况加上await以后 就等待,然后直接取出Task<T>中的T了,这中间的各种可能的隐藏操作被底层编译器,运行时等代替了,比如线程上下文切换等。
要注意其实在实际场景中直接用Task.Run而不加await的情况才是异步的核心需求,异步的意思是同时做不等待。所以只有让很多Task同时开始调用的时候才有价值。
所以在同步调用方调用async方法时,最终还得用.Wait()/Result两种方式对其进行调用,从这里可以看出await其实是在异步线程池上进行的等待。因为现在有很多异步调用终端比如 webapi中的 Controller.Action方法比如 标记为async的Main方法都是异步调用终端所以不需要用上述方式切换回
同步主线程。
//... async Task mAsync(){ await Task1(); await Task2(); //... await Task3(); } void m(){ mAsync().Wait(); }
上述代码是有价值的异步设计为什么呢?因为mAsync方法里同时有多个await在,是await其实是在异步里等待它虽然在等待但是在异步里等待,这还有个好处是它可以在设计时清楚的管理异步调用的次序,要不然异步调用的次序是不可管理的要不然加很多锁或通知机制,而Task很好的把这些机制底层化为自动处理了,那么这些Task1...n之间的任务实际在调用m方法时会全部给到线程池(做统筹)从而能让它们可以在一定程度上“同步执行”来体现异步的价值,以至于在最后mAsync.Wait()时会有较短的耗时和较高的cpu资源利用率,而这就是异步编程的价值所在。
async Task m(){ var time0=DateTime.Now; var t=Task.Delay(5000); await Task.Delay(2200); await Task.Delay(2500); await t; Console.WriteLine(DateTime.Now - time0); } await m();
// 有网友给出的代码经测试结果发现其耗时基本上在5秒多一点,而如果3个await同步执行的话需要9.7秒以上,这大概是因为这3个await之间没有依赖不会Task.ContinueWith(OtherTask)的原因如果存在这样的依赖那么时间就会是9.7以上,所以底层应该对此做了判断,这也就是说在async方法里要多用Task.Run,只要保证其中有一个await就行,await的目的是 Task.ContinueWith 让其返回可以被拿到调用环境里使用否则,则不需要await 而如果async方法有很多Task.Run但其实并不都需要await,只是你写了也无妨因为底层有一定的判断是知道的(上述测试结果就是证明),所以你可以都写,也可以只在最后一个Task.Run()前写await以满足async方法的需要。这样的设计就真正能体现异步编程带来的价值了。