之前看了園子里的一篇文章「async & await的前世今生」,收益頗多。而其中有句話被博主特意用紅色標注,所以留意多看了幾眼,「await 之后不會開啟新的線程(await 從來不會開啟新的線程)」。在MSDN上找到的相關資料也佐證了其正確性——The async and await keyWords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active.(async 和 await 關鍵字不會導致創建其他線程。 因為異步方法不會在其自身線程上運行,因此它不需要多線程。 只有當方法處于活動狀態時,該方法將在當前同步上下文中運行并使用線程上的時間。)
再建立一個Windows Forms應用工程,寫點代碼更形象地說明問題:
PRivate void Form1_Load(object sender, EventArgs e){ PrintDataAsync(); Debug.Print("three");}private async void PrintDataAsync(){ Task<int> result = CalculateDataAsync(); Debug.Print("second"); int data = await result; Debug.Print("last:" + data);}private async Task<int> CalculateDataAsync(){ Debug.Print(string.Format("{0} : {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread)); Debug.Print("first"); int result = 0; for (int i = 0; i < 10; i++) { result += i; } await Task.Delay(1000); Debug.Print("four"); Debug.Print(string.Format("{0} : {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread)); return result;};
程序的結果如預期的一樣,Output窗口中可以看到以下內容:
8 : Falsefirstsecondthreefour8 : Falselast:45
await之前的ManagedThreadId值與之后的ManagedThreadId值一致,IsThreadPoolThread始終是False,說明當前線程沒有發生改變,也沒有產生新的線程。
但如果建立的是Console應用工程,結果就不同了。
static void Main(string[] args){ PrintDataAsync(); Console.WriteLine("three"); Console.Read();}private static async void PrintDataAsync(){ Task<int> result = CalculateDataAsync(); Console.WriteLine("second"); int data = await result; Console.WriteLine("last:" + data);}private static async Task<int> CalculateDataAsync(){ Console.WriteLine(string.Format("{0} : {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread)); Console.WriteLine("first"); int result = 0; for (int i = 0; i < 10; i++) { result += i; } await Task.Delay(1000); Console.WriteLine("four"); Console.WriteLine(string.Format("{0} : {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread)); return result;}
這段代碼的執行結果:
8 : Falsefirstsecondthreefour10 : Truelast:45
ManagedThreadId在await之后發生了變化,IsThreadPoolThread也變為了True,說明不是同一個線程。
為什么會這樣?再看一下MSDN中描述——「The method runs on the current synchronization context and uses time on the thread only when the method is active」,這里涉及到SynchronizationContext對象的使用。
在Windows Forms工程代碼中加入Debug.Print(SynchronizationContext.Current.ToString()); 檢測代碼,其輸出是System.Windows.Forms.WindowsFormsSynchronizationContext。
而如果在Console工程中加入類似的檢測代碼Console.WriteLine(SynchronizationContext.Current.ToString()); 則會拋出空引用異常,因為SynchronizationContext.Current在Console工程中的值為null。
又從MSDN Magazine找到SynchronizationContext相關的文章,其中有介紹到:By convention, if a thread’s current SynchronizationContext is null, then it implicitly has a default SynchronizationContext.(根據慣例,如果一個線程的當前 SynchronizationContext 為 null,那么它隱式具有一個默認 SynchronizationContext。)The default SynchronizationContext is applied to ThreadPool threads unless the code is hosted by asp.net.(默認 SynchronizationContext 應用于 ThreadPool 線程,除非代碼由 ASP.NET 承載。)
這里提到了APS.NET,所以再建個Web Forms應用工程用于驗證:
protected void Page_Load(object sender, EventArgs e){ PrintDataAsync(); Debug.Print("three");}private async void PrintDataAsync(){ Debug.Print(SynchronizationContext.Current.ToString()); Task<int> result = CalculateDataAsync(); Debug.Print("second"); int data = await result; Debug.Print("last:" + data);}private async Task<int> CalculateDataAsync(){ Debug.Print(string.Format("{0} : {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread)); Debug.Print("first"); int result = 0; for (int i = 0; i < 10; i++) { result += i; } await Task.Delay(1000); Debug.Print("four"); Debug.Print(string.Format("{0} : {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread)); return result;}
輸出結果:
System.Web.AspNetSynchronizationContext8 : Truefirstsecondthreefour9 : Truelast:45
ManagedThreadId值發生改變,IsThreadPoolThread始終是True,SynchronizationContext.Current值為System.Web.AspNetSynchronizationContext。
由三次試驗及相關資料可以得出結論,await之后的線程依據SynchronizationContext在不同環境中的不同定義而產生不同的結果。所以「await 之后不會開啟新的線程(await 從來不會開啟新的線程)」的肯定句式改成「await 之后會開啟新的線程嗎? Maybe」這樣的句式更加合適些。
最后補充一點,若是把第一個Windows Forms工程的代碼await Task.Delay(1000);改成await Task.Delay(1000).ConfigureAwait(false); 的話,則可以得到第二個Console工程同樣的結果。
新聞熱點
疑難解答