有時候我們在代碼中要執行一些非常耗時的操作,我們不希望這些操作阻塞調用線程(主線程)的執行,因為調用線程(主線程)可能還有更重要的工作要做,我們希望將這些非常耗時的操作由另外一個線程去執行,這個時候就可以用到await Task.Yield(),它借助了C# 5.0中的異步函數關鍵字await async,將await關鍵字之后的代碼交由線程池中的另一個線程執行(前提是項目的SynchronizationContext.Current為null)。
那么有同學可能會納悶,await Task.Yield()和await Task.CompletedTask有什么不同嗎?
它倆可大不一樣
- Task.CompletedTask本質上來說是返回一個已經完成的Task對象,所以這時如果我們用await關鍵字去等待Task.CompletedTask,.NET Core認為沒有必要再去線程池啟動一個新的線程來執行await關鍵字之后的代碼,所以實際上await Task.CompletedTask之前和之后的代碼是在同一個線程上同步執行的,通俗易懂的說就是單線程的。這也是為什么很多文章說,使用了await async關鍵字並不代表程序就變成異步多線程的了。
- 而Task.Yield()就不一樣了,我們可以理解Task.Yield()是真正使用Task來啟動了一個線程,只不過這個線程什么都沒有干,相當於在使用await Task.Yield()的時候,確實是在用await等待一個還沒有完成的Task對象,所以這時調用線程(主線程)就會立即返回去做其它事情了,當調用線程(主線程)返回后,await等待的Task對象就立即變為完成了,這時await關鍵字之后的代碼由另外一個線程池線程來執行。
下面我用.NET Core控制台項目,寫一個示例代碼來演示await Task.Yield()和await Task.CompletedTask的不同:
using System; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace NetCoreTaskYield { class Program { /// <summary> /// TaskYield使用await Task.Yield(),是真正的異步執行,await關鍵字之前和之后的代碼使用不同的線程執行 /// </summary> static async Task TaskYield() { Console.WriteLine("TaskYield before await, current thread id: {0}", Thread.CurrentThread.ManagedThreadId.ToString()); await Task.Yield();//執行到await Task.Yield()時,調用TaskYield()方法的線程(主線程)立即就返回了,await關鍵字后面的代碼實際上是由另一個線程池線程執行的 //注意Task.Yield()方法返回的不是Task類的對象,而是System.Runtime.CompilerServices.YieldAwaitable結構體的實例 Console.WriteLine("TaskYield after await, current thread id: {0}", Thread.CurrentThread.ManagedThreadId.ToString()); Thread.Sleep(3000);//阻塞線程3秒鍾,模擬耗時的操作 Console.WriteLine("TaskYield finished!"); } /// <summary> /// 模擬TaskYield的異步執行 /// </summary> static Task TaskYieldSimulation() { //模擬TaskYield()方法中,await關鍵字之前的代碼,由調用TaskYieldSimulation()方法的線程(主線程)執行 Console.WriteLine("TaskYieldSimulation before await, current thread id: {0}", Thread.CurrentThread.ManagedThreadId.ToString()); return Task.Run(() => { //使用Task.Run啟動一個新的線程什么都不做,立即完成,相當於就是Task.Yield() }).ContinueWith(t => { //下面模擬的是TaskYield()方法中,await關鍵字之后的代碼,由另一個線程池線程執行 Console.WriteLine("TaskYieldSimulation after await, current thread id: {0}", Thread.CurrentThread.ManagedThreadId.ToString()); Thread.Sleep(3000);//阻塞線程3秒鍾,模擬耗時的操作 Console.WriteLine("TaskYieldSimulation finished!"); }); } /// <summary> /// TaskCompleted使用await Task.CompletedTask,是假的異步執行,實際上是同步執行,await關鍵字之前和之后的代碼使用相同的線程執行 /// </summary> static async Task TaskCompleted() { Console.WriteLine("TaskCompleted before await, current thread id: {0}", Thread.CurrentThread.ManagedThreadId.ToString()); await Task.CompletedTask;//執行到await Task.CompletedTask時,由於await的Task.CompletedTask已經處於完成狀態,所以.NET Core判定await關鍵字后面的代碼還是由調用TaskCompleted()方法的線程(主線程)來執行,所以實際上整個TaskCompleted()方法是單線程同步執行的 Console.WriteLine("TaskCompleted after await, current thread id: {0}", Thread.CurrentThread.ManagedThreadId.ToString()); Thread.Sleep(3000);//阻塞線程3秒鍾,模擬耗時的操作 Console.WriteLine("TaskCompleted finished!"); } static void Main(string[] args) { Console.WriteLine("Main thread id: {0}", Thread.CurrentThread.ManagedThreadId.ToString()); Console.WriteLine("=============================================="); TaskYield().Wait(); Console.WriteLine("=============================================="); TaskCompleted().Wait(); Console.WriteLine("=============================================="); TaskYieldSimulation().Wait(); Console.WriteLine("Press any key to end..."); Console.ReadKey(); } } }
執行結果如下所示:
注意TaskYield()方法是真正的異步執行,TaskYieldSimulation()方法模擬演示了await Task.Yield()異步執行的原理,而TaskCompleted()方法是假的異步執行,實則為同步單線程執行。
參考文獻:
Does await Task.CompletedTask mean the async method will run synchronously?