提到非同步,那麼與之對應的是什麼呢?同步。那麼C#的非同步和同步是如何工作的呢? 首先,我們先來看看慄子: 新建一個控制台應用程式,在Program文件中添加如下代碼: 這個慄子很簡單,定義了兩個方法:TaskOne,TaskTwo。在裡面每隔一秒輸出一次當前時間,和當前線程。TaskOne迴圈5次和T ...
提到非同步,那麼與之對應的是什麼呢?同步。那麼C#的非同步和同步是如何工作的呢?
首先,我們先來看看慄子:
新建一個控制台應用程式,在Program文件中添加如下代碼:
1 static void Main(string[] args) 2 { 3 //計時器 4 Stopwatch watch = new Stopwatch(); 5 //開始計時 6 watch.Start(); 7 Console.WriteLine($"{DateTime.Now.ToString()} 進入Main方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); 8 //調用任務一(同步) 9 TaskOne(); 10 // 調用任務二 11 TaskTwo(); 12 //停止計時 13 watch.Stop(); 14 Console.WriteLine($"{DateTime.Now.ToString()} 退出Main方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); 15 Console.WriteLine($"主線程總耗時:{watch.ElapsedMilliseconds}ms"); 16 Console.ReadKey(); 17 } 18 19 /// <summary> 20 /// 任務一 21 /// </summary> 22 static void TaskOne() 23 { 24 Console.WriteLine($"{DateTime.Now.ToString()} 進入TaskOne方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); 25 for (int i = 0; i < 5; i++) 26 { 27 Console.WriteLine($"{DateTime.Now.ToString()} TaskOne正在執行,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); 28 System.Threading.Thread.Sleep(1000); 29 } 30 Console.WriteLine($"{DateTime.Now.ToString()} 退出TaskOne方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); 31 } 32 /// <summary> 33 /// 任務二 34 /// </summary> 35 static void TaskTwo(){ 36 Console.WriteLine($"{DateTime.Now.ToString()} 進入TaskTwo方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); 37 for (int i = 0; i < 2; i++) 38 { 39 Console.WriteLine($"{DateTime.Now.ToString()} TaskTwo正在執行,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); 40 System.Threading.Thread.Sleep(1000); 41 } 42 Console.WriteLine($"{DateTime.Now.ToString()} 退出TaskTwo方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); 43 }
這個慄子很簡單,定義了兩個方法:TaskOne,TaskTwo。在裡面每隔一秒輸出一次當前時間,和當前線程。TaskOne迴圈5次和TaskOne2次。然後在MAIN函數裡面順序調用,並記錄MAIN函數執行的總耗時時間。F5運行效果如圖:
從圖中可以看出,程式順序執行TaskOne之後,再執行TaskTwo。執行線程未改變。
下麵我們改改代碼,用非同步方式改寫下TaskOne。提到非同步,大家腦海裡隨之浮現的我想會是它吧。關鍵字async。當然與之成對出現的await也不能少了。先看看改寫後的代碼:
/// <summary> /// 任務一(非同步) /// </summary> /// <returns></returns> static async Task<int> TaskOneAsync() { Console.WriteLine($"{DateTime.Now.ToString()} 進入TaskOneAsync方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); var t = Task<int>.Run(() => { var total = 0; for (int i = 0; i < 5; i++) { total++; Console.WriteLine($"{DateTime.Now.ToString()} TaskOneAsync正在執行,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); System.Threading.Thread.Sleep(1000); } return total; }); Console.WriteLine($"{DateTime.Now.ToString()} 退出TaskOneAsync方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); return await t; }
main函數改為調用非同步方法
static void Main(string[] args) { //計時器 Stopwatch watch = new Stopwatch(); //開始計時 watch.Start(); Console.WriteLine($"{DateTime.Now.ToString()} 進入Main方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); //調用任務一(同步) //TaskOne(); //調用任務一(非同步) TaskOneAsync(); // 調用任務二 TaskTwo(); //停止計時 watch.Stop(); Console.WriteLine($"{DateTime.Now.ToString()} 退出Main方法,執行線程:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"主線程總耗時:{watch.ElapsedMilliseconds}ms"); Console.ReadKey(); }
F5運行後效果:
我們可以看到Main函數的執行時間從7082ms變為了2404ms。時間大大的縮短了。但是,在main已經結束後,TaskOneAsync依然還在運行中....,並且TaskOneAsync的執行線程不是主線程9而是10!!
下麵我們來好好梳理下程式的執行過程,看圖便知:
可以看到當程式進入Main方法執行,進入TaskOneAsync後,線程ID依然是9,當遇到Task執行任務創建,並運行。主線程並沒有阻塞,而是單獨開了一個新的線程10去執行TASK任務。主線程依然順序執行,然後退出非同步方法。進入到TaskTwo中執行完畢,最後直到Main方法結束時,由於TaskOneAsync耗時較長,線程10依然繼續在執行Task。直到Task結束。其實系統,在Task任務Run的時候,已經新開了一個線程執行Task裡面的任務,然後主線程繼續執行TaskTwo,在TaskTwo執行這段期間,任務TaskOneAsync也在另一個線程同時j執行。可見,Task會新開一個線程執行命令,當前線程不會被阻塞,因此Main線程其實根本沒有像同步方式一樣執行最耗時的TaskOneAsync裡面的Task,而是交給了另外一個線程執行,這就是主線程執行時間,大大縮短的原因。因此,這種處理機制,對於用戶體驗,是比較友好的。其實,在我們開發中,常常見到以async結尾的方法。最常見的應該是IO讀取,寫入,以及 http資源請求相關類庫方法。因為這些都是比較耗時的,一般耗時的工作,為了不影響主線程響應,我們一般都採用非同步方式進行處理。
那麼,當我們主線程,需要獲取Task任務返回結果時,主線程會阻塞線程等待其結果返回後,再繼續執行下去。改下Main方法裡面的代碼,驗證一下:
如圖,得以驗證,主線程阻塞了線程,等待Task執行完畢後,再繼續執行。
歸納總結,非同步和同步,我是這樣理解的:
同步:一段代碼指令,在同一線程上,被順序執行,中間沒有插隊。就好比去電影院買票,有很多人(待執行的指令),但是只有一個視窗(一個線程,一般指主線程)。後面的人,只能等前面的人買了票,走了,才能前一步,他們的步調是一致。所以,稱之為同步。
非同步:一段代碼指令,在執行的時候,其中一些指令與指令之間,被執行的時間點一樣,但是操作其執行的線程不一樣。兩者存在一段時間的並行現象。好比電影院看到排隊買票的人越來越多,經理馬上又新安排了一個售票員開了一個新視窗(開新線程)售票,把原來排隊的人(待執行的指令),轉移了一部分到新的視窗繼續排隊買票。這樣原來售票視窗(主線程)的作業任務以及時間,則相應減少了。
非同步方式其實是一種處理機制,它有好處,也有弊端。如果我們無端的濫用,會起反作用。因為,新開線程會消耗線程資源。所以,秉承一個原則:在不影響主線程響應前提下,能不用則不用。
以上都是個人見解,如有錯誤,還望指出,望不吝賜教~~