之前看了園子裡的一篇文章「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 : False first second three four 8 : False last: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 : False first second three four 10 : True last: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.AspNetSynchronizationContext 8 : True first second three four 9 : True last: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工程同樣的結果。
其實await和wait的用法差不多。await是wait改進過來,現在開發一多半都用await,因為await加入了lock方法
Lock 替代了 synchronized 方法和語句的使用
是因為這些操作還是被派給了UI線程來執行。
await可以被理解成.NET 4中的CallBack:當任務結束後會觸發一個完成事件。使用await的目的是await標記的代碼在完成事件被觸發以後再執行。應該不會影響到UI的卡與不卡。
試著把LetFeedShowAsync(int index)被另一線程調用,看看能否解決你的問題。