寫第一篇《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僅是一個異步的寫法,而Thread\BackgroundWorker和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; }
本篇應不是最終的結果,后面如有進一步的發現仍會更新下去,批判的暴風雨還在繼續……