批判“await使用中的阻塞和並發”——對asyc/await這對基友的誤會和更正


  寫第一篇《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菊苣的指點,他指出了我第二篇中仍存在的誤區和錯誤。下面讓我們來領略一下大牛的風采。首先從上篇我給出的結論入手:

  1. async用於異步,可以優美的替代Thread、BackgroundWorker和Task.Run等寫法。
  2. await用於等待。一定是在你主動希望阻塞並等待返回結果時才使用。
  3. 在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;
}

  本篇應不是最終的結果,后面如有進一步的發現仍會更新下去,批判的暴風雨還在繼續……

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM