本篇部落格來談一下我對c#中的async和awaite關鍵字的理解。先來聊聊我在理解這個非同步編程機制時的困惑吧。
我看了使用 Async 和 Await 的非同步編程(C# 和 Visual Basic)這篇文章後,感覺so easy,非同步方法呼叫返回一個Task<TResult>對象,憑著我對Task類的“深入理解”,我就斷定:當調用一個同步方法時,由於同步方法返回的是Task,.net自動就讓這個task開始在後台線程中運行,然後這個同步方法直接返回,當調用await時,就等於調用了task的Wait()方法,會阻塞當前線程等待task返回結果。然後憑著我的理解,就寫出了下面的代碼
class Program{ static void Main() { MyAsyncMethod(); Console.ReadKey(); } static async void MyAsyncMethod() { Console.WriteLine("call MyAsyncMethod....."); Task<int> calculateResult = CalculateAsync(); Console.WriteLine("MyAsyncMethod was called......."); int result = await calculateResult; Console.WriteLine($"result is {result}"); } static async Task<int> CalculateAsync() { int count = 5; for (int i = 0; i < 5; i++) { Thread.Sleep(5000); Console.WriteLine($"calculating step {i + 1}"); } Random random = new Random(); return random.Next(); }}
按照我上面的理解,第11行和第13行會被連續輸出出來,但實際的輸出確實
call MyAsyncMethod.....calculating step 1calculating step 2calculating step 3calculating step 4calculating step 5MyAsyncMethod was called.......result is 900866582
也就是說,直到CalculateAsync()返回後,第13行代碼才被執行。不是說好了使用async就是非同步編程嗎。這明明是逐步執行的。微軟大騙子。
再看看文檔:
非同步方法呼叫旨在成為非阻止操作。 非同步方法呼叫中的 await 運算式在等待的任務正在運行時不會阻止當前線程。 相反,運算式在繼續時註冊方法的其餘部分並將控制項返回到非同步方法呼叫的調用方。
async 和 await 關鍵字不會導致建立其他線程。 因為非同步方法呼叫不會在其自身線程上運行,因此它不需要多線程。 只有當方法處於活動狀態時,該方法將在當前同步上下文中運行並使用線程上的時間。 可以使用 Task.Run 將佔用大量 CPU 的工作移到後台線程,但是後台線程不會協助正在等待結果的進程變為可用狀態。
對於非同步編程而言,該基於非同步方法優於幾乎每個用例中的現有方法。 具體而言,此方法比 BackgroundWorker 更適用於 IO 綁定的操作,因為此代碼更簡單且無需防止競爭條件。 結合 Task.Run 使用時,非同步編程比 BackgroundWorker 更適用於 CPU 綁定的操作,因為非同步編程將運行代碼的協調細節與 Task.Run 傳輸至線程池的工作區分開來。
好像看明白了些什麼。async和await不會建立其它線程,非同步方法呼叫需要將耗時的工作放到Task.Run中執行。好吧,修改下代碼
class Program { static void Main() { MyAsyncMethod(); string input = ""; while ((input = Console.ReadLine()) != "q") { Console.WriteLine($"your input is {input}"); } } static async void MyAsyncMethod() { Console.WriteLine("call MyAsyncMethod....."); Task<int> calculateResult = CalculateAsync(); Console.WriteLine("MyAsyncMethod was called......."); int result = await calculateResult; Console.WriteLine($"result is {result}"); } static async Task<int> CalculateAsync() { Console.WriteLine("calculation begin....."); int t = await Task.Run(() => Calculate()); Console.WriteLine("calculation complete....."); return t; } static int Calculate() { // Compute total count of digits in strings. int count = 5; for (int i = 0; i < 5; i++) { Thread.Sleep(5000); Console.WriteLine($"calculating step {i + 1}"); } Random random = new Random(); return random.Next(); } }
這段代碼的運行結果是:
call MyAsyncMethod.....calculation begin.....MyAsyncMethod was called.......calculating step 1calculating step 2calculating step 3calculating step 4calculating step 5calculation complete.....result is 1534528428
計算的過程中還可以相應使用者輸入,比如:
call MyAsyncMethod.....calculation begin.....MyAsyncMethod was called.......calculating step 1helloyour input is hellocalculating step 2下班了your input is 下班了calculating step 3calculating step 4worldyour input is worldcalculating step 5calculation complete.....result is 1805505488
上面的代碼其實應該從下往上看。首先Calculate()是一個耗時的任務,CalculateAsync()是一個非同步方法呼叫,當然,還是要使用Task.Run()的。MyAsyncMethod()方法來調用CalculateAsync()方法。在Main方法中,調用MyAsyncMethod()時會直接返回,不阻塞當前線程,因為當前線程還需要接受使用者的輸入。
最後,我把async和await理解為一種更方便地進行非同步編程的機制,本身並不建立線程,但卻實現了非同步,非常簡單。但是,有非同步必然有多線程,一個線程不可能同時運行兩個方法,只是我們以後在編寫類的時候,可以通過Task.Run()來自己實現一些非同步方法,定義為async,這樣可以簡化UI線程的編程。