之前看了園子里的一篇文章「async & await的前世今生」,收益頗多。而其中有句話被博主特意用紅色標(biāo)注,所以留意多看了幾眼,「await 之后不會開啟新的線程(await 從來不會開啟新的線程)」。在MSDN上找到的相關(guān)資料也佐證了其正確性——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 關(guān)鍵字不會導(dǎo)致創(chuàng)建其他線程。 因為異步方法不會在其自身線程上運行,因此它不需要多線程。 只有當(dāng)方法處于活動狀態(tài)時,該方法將在當(dāng)前同步上下文中運行并使用線程上的時間。)
再建立一個Windows Forms應(yīng)用工程,寫點代碼更形象地說明問題:
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;};
程序的結(jié)果如預(yù)期的一樣,Output窗口中可以看到以下內(nèi)容:
8 : Falsefirstsecondthreefour8 : Falselast:45
await之前的ManagedThreadId值與之后的ManagedThreadId值一致,IsThreadPoolThread始終是False,說明當(dāng)前線程沒有發(fā)生改變,也沒有產(chǎn)生新的線程。
但如果建立的是Console應(yīng)用工程,結(jié)果就不同了。
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;}
這段代碼的執(zhí)行結(jié)果:
8 : Falsefirstsecondthreefour10 : Truelast:45
ManagedThreadId在await之后發(fā)生了變化,IsThreadPoolThread也變?yōu)榱薚rue,說明不是同一個線程。
為什么會這樣?再看一下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相關(guān)的文章,其中有介紹到:By convention, if a thread’s current SynchronizationContext is null, then it implicitly has a default SynchronizationContext.(根據(jù)慣例,如果一個線程的當(dāng)前 SynchronizationContext 為 null,那么它隱式具有一個默認(rèn) SynchronizationContext。)The default SynchronizationContext is applied to ThreadPool threads unless the code is hosted by asp.net.(默認(rèn) SynchronizationContext 應(yīng)用于 ThreadPool 線程,除非代碼由 ASP.NET 承載。)
這里提到了APS.NET,所以再建個Web Forms應(yīng)用工程用于驗證:
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;}
輸出結(jié)果:
System.Web.AspNetSynchronizationContext8 : Truefirstsecondthreefour9 : Truelast:45
ManagedThreadId值發(fā)生改變,IsThreadPoolThread始終是True,SynchronizationContext.Current值為System.Web.AspNetSynchronizationContext。
由三次試驗及相關(guān)資料可以得出結(jié)論,await之后的線程依據(jù)SynchronizationContext在不同環(huán)境中的不同定義而產(chǎn)生不同的結(jié)果。所以「await 之后不會開啟新的線程(await 從來不會開啟新的線程)」的肯定句式改成「await 之后會開啟新的線程嗎? Maybe」這樣的句式更加合適些。
最后補充一點,若是把第一個Windows Forms工程的代碼await Task.Delay(1000);改成await Task.Delay(1000).ConfigureAwait(false); 的話,則可以得到第二個Console工程同樣的結(jié)果。
新聞熱點
疑難解答