写第一篇《await使用中的阻塞和并发》的时候还自信满满,觉得写的真不错,结果漏洞百出……
更正第二篇《await使用中的阻塞和并发(二)》的时候觉得这回不会再错了……
结果我正在写第三篇,而且连篇名都不敢延用了……
首先完善第二篇对Foreach(Action<T>)的拆解,用很厉害的小兄弟geelaw的话说就是“是用另一个方法返回λ表达式创建的委托,并未把λ表达式换成方法。”惭愧啊,在小兄弟的指点下,修改代码如下,在Foreach(Action)这个话题上算是圆满了:
public void AwaitFuncTaskListNoLambda() { var funcList = new List<Func<Task>>() { Delay3000Async, Delay2000Async, Delay1000Async }; //funcList.ForEach(async _ => await _()); //funcList.ForEach(AwaitAction()); //foreach (var func in funcList) //{ // AwaitAction()(func); //} Action<Func<Task>> L = this.Xxx; foreach (var func in funcList) { L(func); } } async void Xxx(Func<Task> ft) { await ft(); } public Action<Func<Task>> AwaitAction() { return async _ => await _(); }
接下来的文字是本篇的重点,来自redjackwang菊苣的指点,他指出了我第二篇中仍存在的误区和错误。下面让我们来领略一下大牛的风采。首先从上篇我给出的结论入手:
- async用于异步,可以优美的替代Thread、BackgroundWorker和Task.Run等写法。
- await用于等待。一定是在你主动希望阻塞并等待返回结果时才使用。
- 在async方法里,Task在创建时就开始运行了。
第一点需要指出这里的替代仅仅是写法的变化,async方法内部具体实现可能仍使用Thread等多线程技术来实现。这里需要明确async/await仅是一个异步的写法,而ThreadBackgroundWorker和Task是多线程。
关于第二点,有两位菊苣分别提出异议。redjackwang的原话是“await并不会阻塞任何线程。await只是把当前方法状态保存并立即返回,不管是主线程还是后台线程都不会阻塞,而是在完成时call一个回调。”胖胖的韦恩卑鄙是这么说的:“说await是主动阻塞问题很大。那叫做响应式休眠?”
至于第三点,那是MSDN坑我,redjackwang大人竟然从MSDN上找到了更详细的答案来证明我给出的链接是错误的。在此一定要共享出来给各位,不要被我上篇错误的结论误导了,正确的表述如下:
Tasks created by its public constructors are referred to as “cold” tasks, in that they begin their life cycle in the non-scheduled TaskStatus.Created state, and it’s not until Start is called on these instances that they progress to being scheduled. All other tasks begin their life cycle in a “hot” state, meaning that their asynchronous execution has already been initiated and their TaskStatus is an enumeration value other than Created.
All tasks returned from TAP methods must be “hot.” If a TAP method internally uses a Task’s constructor to instantiate the task to be returned, the TAP method must call Start on the Task object prior to returning it. Consumers of a TAP method may safely assume that the returned task is “hot,” and should not attempt to call Start on any Task returned from a TAP method. Calling Start on a “hot” task will result in an InvalidOperationException (this check is handled automatically by the Task class).
为此redjackwang还给出了证明的例子,在这个例子中,如果注释掉start方法,Task是不会自动运行的。结论是Task如果是通过构造函数创建的,状态是cold的,不会自动运行,而前一篇都是通过返回Task的方法创建出来的,状态是hot,所以自动运行了,和在不在async方法中没有关系。
private async void Button_Click(object sender, RoutedEventArgs e) { var v = await Foo(); this.Title = v.ToString(); } private async Task<long> Foo() { var t = new Task<long>(() => { long z = 0; for (int i = 0; i < 100000; i++) { z += i; } return z; }); //t.Start(); return await t; }
本篇应不是最终的结果,后面如有进一步的发现仍会更新下去,批判的暴风雨还在继续……