1. 程式人生 > >await Task.Yield()和await Task.CompletedTask有什麼不同

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(); 超簡單理解!

&n