await Task.Yield()和await Task.CompletedTask有什么不同


有時候我們在代碼中要執行一些非常耗時的操作,我們不希望這些操作阻塞調用線程(主線程)的執行,因為調用線程(主線程)可能還有更重要的工作要做,我們希望將這些非常耗時的操作由另外一個線程去執行,這個時候就可以用到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()方法是假的異步執行,實則為同步單線程執行。

 

參考文獻:

終於明白了 C# 中 Task.Yield 的用途

await Task.Yield(); 超簡單理解!

Does await Task.CompletedTask mean the async method will run synchronously? 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM