C#關(guān)于Task.Yeild()函數(shù)的討論
在與同事討論async/await內(nèi)部實現(xiàn)的時候,突然想到Task.Yeild()這個函數(shù),為什么呢,了解一點C#async/await內(nèi)部機(jī)制的都知道,在await一個異步任務(wù)(函數(shù))的時候,它會先判斷該Task是否已經(jīng)完成,如果已經(jīng)完成,則繼續(xù)執(zhí)行下去,不會返回到調(diào)用方,原因是盡量避免線程切換,因為await后面部分的代碼很可能是另一個不同的線程執(zhí)行,而Task.Yeild()則可以強(qiáng)制回到調(diào)用方,或者說主動讓出執(zhí)行權(quán),給其他Task執(zhí)行的機(jī)會,可以把Task理解為協(xié)程,Task.Yeild()和Thread.sleep(0)有點相同。
為了證明我的結(jié)論成立,請看代碼:
public static async Task Test1()
{
await Task.CompletedTask;
Thread.Sleep(1000);
Console.WriteLine("Test1任務(wù)完成");
}
public static async Task Test2()
{
await Task.Delay(1);
Thread.Sleep(1000);
Console.WriteLine("Test2任務(wù)完成");
}
public static async Task Test3()
{
await Task.Yield();
Thread.Sleep(1000);
Console.WriteLine("Test3任務(wù)完成");
}
static void Main(string[] args)
{
Console.WriteLine(DateTime.Now);
_ = Test1();
Console.WriteLine(DateTime.Now);
Console.ReadLine();
}
按照開頭的理論,Test1()異步函數(shù)由于await了一個已經(jīng)完成的任務(wù),所以會繼續(xù)往下執(zhí)行,阻塞1秒鐘,然后回到調(diào)用方,打印的時間之差會相隔一秒。

Test2()異步函數(shù)由于await了一個未完成的任務(wù)(1ms對于CPU來說是很長的了),所以會返回調(diào)用方,然后打印相同的時間,一秒鐘之后會打印執(zhí)行完畢。

Test3()調(diào)用了Task.Yeild()函數(shù),主動讓出執(zhí)行權(quán),所以會直接返回調(diào)用方,然后打印相同的時間,一秒之后會打印執(zhí)行完畢。

可以看到,開頭的結(jié)論是正確的。那么,有什么意義呢?Yeild的意思在這里其實就是退讓,讓出的意思,讓出什么呢?就是讓出執(zhí)行權(quán),這與Thread.sleep(0)讓出CPU執(zhí)行權(quán)給其他線程(前提是有其他線程競爭)有機(jī)會執(zhí)行是一個道理。
請看我的例子:
public static async Task OP1()
{
while (true)
{
await Task.Yield();//這里會捕捉同步上下文,由于是控制臺程序,沒有同步上下文,所以默認(rèn)的線程池任務(wù)調(diào)度器變成同步上下文
//也就是說后面的代碼將會在線程池上執(zhí)行,由于線程池工作線程數(shù)量設(shè)置為1,所以必須主動讓出執(zhí)行權(quán),讓其他的
//任務(wù)有執(zhí)行的機(jī)會
Console.WriteLine("OP1在執(zhí)行");
Thread.Sleep(1000);//模擬一些需要占用CPU的操作
}
}
public static async Task OP2()
{
while (true)
{
await Task.Yield();
Console.WriteLine("OP2在執(zhí)行");
Thread.Sleep(1000);
}
}
static async Task Main(string[] args)
{
ThreadPool.SetMinThreads(1, 1);
ThreadPool.SetMaxThreads(1, 1);
//Task.Run()方法默認(rèn)使用線程池任務(wù)調(diào)度器執(zhí)行任務(wù),由于主線程不是線程池線程,所以使用Task.Run()
var t = Task.Run(async () =>
{
var t1 = OP1();
var t2 = OP2();
await Task.WhenAll(t1, t2);
});
await t;
Console.ReadLine();
}

可以看出OP1()和OP2()兩個協(xié)程(Task)互相爭用一個線程(用戶模式下的CPU),如果不主動讓出執(zhí)行權(quán),另一個協(xié)程(Task)將不會有機(jī)會執(zhí)行。
例如:
public static async Task OP2()
{
while (true)
{
await Task.CompletedTask;//或者是直接去掉
Console.WriteLine($"OP2在執(zhí)行 {DateTime.Now}");
Thread.Sleep(1000);
}
}
這樣OP1()將永遠(yuǎn)不會有機(jī)會執(zhí)行。

以上就是C#中關(guān)于Task.Yeild()函數(shù)的討論的詳細(xì)內(nèi)容,更多關(guān)于C# Task.Yeild()的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
可替代log4j日志的c#簡單日志類隊列實現(xiàn)類代碼分享
簡單日志類隊列實現(xiàn)。可按天周月年大小分割文件??珊唵翁娲鷏og4j2013-12-12
C#探秘系列(一)——ToDictionary,ToLookup
這個系列我們看看C#中有哪些我們知道,但是又不知道怎么用,又或者懶得去了解的東西,比如這篇我們要介紹的toDictionary和ToLookup。2014-05-05
WindowsForm移動一個沒有標(biāo)題欄的窗口的方法
這篇文章主要介紹了WindowsForm移動一個沒有標(biāo)題欄的窗口的方法,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-07-07

