1. 程式人生 > >await使用中的阻塞和並發

await使用中的阻塞和並發

guid basic 等待 doesn 運行 XML point 之前 ui線程

好吧,不加點陳述不讓發首頁。那我們來陳述一下本篇提到的問題和對應的方法。

在.NET4.5中,我們可以配合使用async和await兩個關鍵字,來以寫同步代碼的方式,實現異步的操作。

好處我目前看來有兩點:

1.不會阻塞UI線程。一旦UI線程不能及時響應,會極大的影響用戶體驗,這點在手機和平板的APP上尤為重要。

2.代碼簡潔。

  • 相對基於event的異步方式,在多次回調的情況下(比如需要多次調web service,且後續調用基於前次調用的結果)特別明顯。可以將多個+=Completed方法合並到一起。
  • 相對於Begin/End的異步方式,避免了N重且不能對齊的大括號。

在同一個方法裏存在多個await的情況下,如後續Async方法無需等待之前的Aysnc方法返回的結果,會隱式的以並行方式來運行後面的Async方法。

值得註意的是錯誤的寫法會導致非預期的阻塞,下文會以簡單的例子來討論在使用await的情況下,怎樣實現多個Task的並發執行。

我們這裏先看幾個方法定義:

        static async Task Delay3000Async()
        {
            await Task.Delay(3000);
            Console.WriteLine(3000);
            Console.WriteLine(DateTime.Now);
        }

        static async Task Delay2000Async()
        {
            await Task.Delay(2000);
            Console.WriteLine(2000);
            Console.WriteLine(DateTime.Now);
        }

        static async Task Delay1000Async()
        {
            await Task.Delay(1000);
            Console.WriteLine(1000);
            Console.WriteLine(DateTime.Now);
        }

作用很簡單,僅僅是起到延遲的作用。我們再來看如下寫法的調用

            Console.WriteLine(DateTime.Now);
 
            new Action(async () =>
            {
                await Delay3000Async();
                await Delay2000Async();
                await Delay1000Async();
            })();        
技術分享圖片

結果如圖,可以看出3個await是線性執行,第一個await會返回並阻止接下來的await後面的方法。這應該不是我們想要的效果,畢竟後面的方法並不依賴第一個方法的執行。

技術分享圖片

我們換一種寫法,再運行一次程序:

            var task3 = Delay3000Async();
            var task2 = Delay2000Async();
            var task1 = Delay1000Async();

            new Action(async () =>
            {
                await task3;
                await task2;
                await task1;
            })();    

技術分享圖片

可以看到3個await後面的方法是並行執行的。MSDN的解釋如下:

In an async method, tasks are started when they’re created. The Await (Visual Basic) or await (C#) operator is applied to the task at the point in the method where processing can’t continue until the task finishes.

However, you can separate creating the task from awaiting the task if your program has other work to accomplish that doesn’t depend on the completion of the task.

Between starting a task and awaiting it, you can start other tasks. The additional tasks implicitly run in parallel, but no additional threads are created.

MSDN原文傳送門

到這裏並沒有結束 ,後面還有一些奇怪的事情:

            var tasks = new List<Task>
            {
                Delay3000Async(),
                Delay2000Async(),
                Delay1000Async()
            };

            tasks.ForEach(async _ => await _);

這個結果和上面是一樣的,可以並行執行。這並不奇怪,我們僅僅是把Task放到一個List裏,按照MSDN的說法,Task在被我們放進List時就被創建,且並發執行了。

那麽我們再來一個List,這回放進去的不是Task,而是Func<Task>:

            var funcList = new List<Func<Task>>()
            {
                Delay3000Async,
                Delay2000Async,
                Delay1000Async
            };

            funcList.ForEach(async _ => await _());

仍然可以並發執行,看上去似乎沒什麽問題,但是作為Func<Task>來存儲到List裏,應該是沒有被創建出來才對。為什麽會能夠並發呢?

我們再來看最後一組寫法:

            Func<Task> func3 = Delay3000Async;
            Func<Task> func2 = Delay2000Async;
            Func<Task> func1 = Delay1000Async;

            new Action(async () =>
            {
                await func3();
                await func2();
                await func1();
            }
            )();

意料之中的,以上的寫法並不能夠做到並發執行。而是需要按順序執行func3,func2和func1。這很好解釋,因為: a task is awaited as soon as it’s created。我們在創建Task之後立即就要求阻塞並等待完成才進行下一步。

技術分享圖片

寫到這裏的時候對List<Func<Task>>的例子開始迷糊了。參考了Reflector反編譯的結果……我想說……沒看出來有什麽區別……本篇先到這裏。一旦琢磨出個所以然,我再發第二篇好了。

還恭請各位高人不吝賜教,多多提點。

補充:對List<Func<Task>>的那個例子,我懷疑是Foreach這個擴展方法在偷偷做了優化。故增加了如下的試驗:

       static async Task TestForeach()
        {
            var funcList = new List<Func<Task>>()
            {
                Delay3000Async,
                Delay2000Async,
                Delay1000Async
            };

            foreach (var item in funcList)
            {
          //這裏幹了件蠢事,不要主動阻塞在這裏,就可以並發了…… await item(); } }

試驗結果表明用foreach來寫的話,確實是做不到並行執行的。那麽就需要去看一下Foreach的背後到底發生了什麽。我還要研究研究才能寫下一篇……

哈哈哈哈,幹了件蠢事情……

代碼下載:https://github.com/kuaidaoyanglang/TaskTest

await使用中的阻塞和並發