十年河東,十年河西,莫欺少年窮
學無止境,精益求精
1、簡介
從 VS 2012 開始,新引入了一個簡化的方法,稱為異步編程。我們在 >= .NETFRM 4.5 中和 Windows 運行時中使用異步,編譯器它會幫助了我們降低了曾經進行的高難度異步代碼編寫的工作,但邏輯結構卻類似於同步代碼。因此,我們僅需要進行一小部分編程的工作就可以獲得異步編程的所有優點。
對於同步的代碼,大家肯定都不陌生,因為我們平常寫的代碼大部分都是同步的,然而同步代碼卻存在一個很嚴重的問題,例如我們向一個Web服務器發出一個請求時,如果我們發出請求的代碼是同步實現的話,這時候我們的應用程序就會處於等待狀態,直到收回一個響應信息為止,然而在這個等待的狀態,對於用戶不能操作任何的UI界面以及也沒有任何的消息,如果我們試圖去操作界面時,此時我們就會看到”應用程序為響應”的信息(在應用程序的窗口旁),相信大家在平常使用桌面軟件或者訪問web的時候,肯定都遇到過這樣類似的情況的,對於這個,大家肯定會覺得看上去非常不舒服。引起這個原因正是因為代碼的實現是同步實現的,所以在沒有得到一個響應消息之前,界面就成了一個”卡死”狀態了,所以這對於用戶來說肯定是不可接受的
2、優勢
異步編程最大的優勢其實就是提供系統執行效率,畢竟一個串行執行的程序不如並行來的快。譬如:一個人要干十件事情不如十個人各干一件事情效率高。
3、關鍵字
C# 中的 async 和 await 關鍵字都是異步編程的核心。通過使用這兩個關鍵字,我們就可以在 .NET 輕松創建異步方法。
4、返回值類型
4.1、Void
如果在觸發后,你懶得管,請使用 void。
void返回類型主要用在事件處理程序中,一種稱為“fire and forget”(觸發並忘記)的活動的方法。除了它之外,我們都應該盡可能是用Task,作為我們異步方法的返回值。
4.2、Task
你如果只是想知道執行的狀態,而不需要一個具體的返回結果時,請使用Task。
與void對比呢,Task可以使用await進行等待新線程執行完畢。而void不需要等待。
4.3、Task<TResult>
當你添加async關鍵字后,需要返回一個將用於后續操作的對象,請使用Task<TResult>。
主要有兩種方式獲取結果值,一個是使用Result屬性,一個是使用await。他們的區別在於:如果你使用的是Result,它帶有阻塞性,即在任務完成之前進行訪問讀取它,當前處於活動狀態的線程都會出現阻塞的情形,一直到結果值可用。所以,在絕大多數情況下,除非你有絕對的理由告訴自己,否則都應該使用await,而不是屬性Result來讀取結果值。
5、范例
再進行范例之前,先寫一個錯誤的異步方法,如下:

public static async Task SyncExec_3() { Proc(); } public static void Proc() { for (int i = 0; i < 1000; i++) { Console.WriteLine(i); } }
由上圖截圖可以,在異步方法內,需要使用await關鍵字,否則方法會同步執行。
不是說你把一個方法標記成async這個方法就成了異步調用的方法了。async這個關鍵詞其實反而是可以省略的,這個關鍵詞存在的意義是為了向下兼容,為await提供上下文而已。
如下兩個方法其實是一樣的

Task<int> DelayAndCalculate1(int a, int b) { return Task.Delay(1000).ContinueWith(t => a + b); } async Task<int> DelayAndCalculate2(int a, int b) { await Task.Delay(1000); return a + b; }
那么,既然async是可以省略的,那么await可以省略嗎?答案是不可以,否則你的方法會被編譯警告,會成為一個同步方法。
其實真正重要的是await,有沒有async反而確實不重要。既然微軟提供了這樣的語法糖,所以建議大家在寫異步方法是加上async。
下面我們通過實例來說明異步編程,如下:
5.1、返回值為Task的程序具體返回了什么?

public static async Task SyncExec_2() { await Task.Run(() => { Proc(); }); }
通過調試,快速監視,得到如下消息:
其實返回值為Task的方法中什么也沒返回,但是我們確定接收到他的返回值,這點似乎是個矛盾點。根據VS快速監視截圖,我們發現我們接收的東西是一個上下文線程。
5.2、異步執行的順序
核心代碼:

using System; using System.Diagnostics; using System.Threading.Tasks; namespace ConsoleCore { class Program { static void Main(string[] args) { var Result = ComplexWorkFlow(); Console.WriteLine("執行結束"); Console.Read(); } /// <summary> /// 加法 /// </summary> /// <param name="num1"></param> /// <param name="num2"></param> /// <returns></returns> public static int GetNum_Add(int num1, int num2) { return num1+ num2; } /// <summary> /// 減法 /// </summary> /// <param name="num1"></param> /// <param name="num2"></param> /// <returns></returns> public static int GetNum_Sub(int num1,int num2) { return num1 - num2; } /// <summary> /// 乘法 /// </summary> /// <param name="num1"></param> /// <param name="num2"></param> /// <returns></returns> public static int GetNum_Mul(int num1,int num2) { return num1 * num2; } /// <summary> /// 除法 /// </summary> /// <param name="num1"></param> /// <param name="num2"></param> /// <returns></returns> public static int GetNum_Cal(int num1, int num2) { return num1 / num2; } /// <summary> /// 10 /// </summary> /// <returns></returns> public static async Task<int> DoTask1() { var result = 0; await Task.Run(() => { result = GetNum_Add(5, 5); }); Console.WriteLine("任務 DoTask1 執行完畢,結果為:" + result); return result; } /// <summary> /// 90 /// </summary> /// <returns></returns> public static async Task<int> DoTask2() { var result = 0; await Task.Run(() => { result = GetNum_Sub(100,10); }); Console.WriteLine("任務 DoTask2 執行完畢,結果為:" + result); return result; } /// <summary> /// 100 /// </summary> /// <param name="A"></param> /// <returns></returns> public static async Task<int> DoTask3UseResultOfTask1(int A) { var result = 0; await Task.Run(() => { result = GetNum_Mul(A, A); }); Console.WriteLine("任務 DoTask3UseResultOfTask1 執行完畢,結果為:" + result); return result; } /// <summary> /// 45 /// </summary> /// <param name="A"></param> /// <returns></returns> public static async Task<int> DoTask4UseResultOfTask2(int A) { var result = 0; await Task.Run(() => { result = GetNum_Cal(A, 2); }); Console.WriteLine("任務 DoTask4UseResultOfTask2 執行完畢,結果為:" + result); return result; } /// <summary> /// 兩者平方和 /// </summary> /// <param name="A"></param> /// <param name="B"></param> /// <returns></returns> public static async Task<int> DoTask5(int A, int B) { var result = 0; await Task.Run(() => { result = A * A + B * B; }); Console.WriteLine("最終的結果為:" + result); return result; } public static async Task<int> ComplexWorkFlow() { Task<int> task1 = DoTask1(); Task<int> task2 = DoTask2(); Task<int> task3 = DoTask3UseResultOfTask1(await task1); Task<int> task4 = DoTask4UseResultOfTask2(await task2); return await DoTask5(await task3, await task4); } } }

發現沒有:執行結束反而先執行完畢了,呵呵呵,意不意外?
繼續執行一次:
這次執行結束換到了第二行,呵呵,意不意外?
再執行一次:
額,(⊙o⊙)…又跑到第三行了,不執行了,大概說下吧。
task1和task2並行,task3和task4並行,但是task1肯定會在task3之后,Task2肯定會在task4之后,task3 和 task4 肯定會在Task5之后,至於輸出的執行結束這段話,可能出現在任何位置。哈哈。就這么任性。
上述探討了異步方法,但:
首先要知道async await解決了什么問題,不要為了異步而異步,針對高密集的cpu計算異步沒太大意義,甚至可能有性能損耗。
其次說async await的實現,就以你的代碼為例,如果沒有async await的話代碼執行步驟就不說了,在有async await后就不一樣,一旦調用一個async方法,就是告知,這里我可能需要點時間來處理,你先繼續往后走吧(比如io操作),這塊執行線程就會繼續往后跑而不再關心async方法的返回直到看到對應的await后,就停下來等着await對應的task執行完(你async await的代碼在編譯后會變成一個狀態機,這個你可以看下你這段代碼在il中的實現),執行完后就會從對應的task展開(unwarp)拿到原始結果(比如你代碼中幾個await的地方),這里額外就可以回答你第Task和async await的差異,async await的表現是基於Task,但顯式的Task會根據TaskScheduler啟動線程,而async await不會額外新起線程,async await會從當前可用線程中找空閑的線程來執行,由於所有線程都沒閑着(沒有所謂的等待,特別是耗時的io等待),因此服務的吞吐量會高很多(適用於高io場景)
其實上面也解釋了多線程和async await的差異了,多線程不等同於異步,你拿TaskFactory或者ThreadPool搞一堆線程,它們都做着同步的工作還是會在執行的時候阻塞,線程大量的時間就這樣白白浪費在了等待響應上了。
參考文檔:https://www.zhihu.com/question/58922017