C#異步編程由淺入深(三)之詳解Awaiter
上一篇末尾提到了Awaiter這個(gè)類型,上一篇說了,能await的對象,必須包含GetAwaiter()方法,不清楚的朋友可以看上篇文章。那么,Awaiter到底有什么特別之處呢?
首先,從上篇文章我們知道,一個(gè)Awaiter必須實(shí)現(xiàn)INotifyCompletion接口,這個(gè)接口定義如下:
namespace System.Runtime.CompilerServices { /// <summary> /// Represents an operation that will schedule continuations when the operation completes. /// </summary> public interface INotifyCompletion { /// <summary>Schedules the continuation action to be invoked when the instance completes.</summary> /// <param name="continuation">The action to invoke when the operation completes.</param> /// <exception cref="System.ArgumentNullException">The <paramref name="continuation"/> argument is null (Nothing in Visual Basic).</exception> void OnCompleted(Action continuation); } }
除此之外還必須包含IsCompleted屬性和包含GetResult()方法。
注意OnCompleted的參數(shù)是一個(gè)Action委托,并且不出意外的話,委托里面總會(huì)有一個(gè)地方調(diào)用一個(gè)MoveNext()方法,它推動(dòng)狀態(tài)機(jī)到達(dá)下一個(gè)狀態(tài),然后執(zhí)行下一個(gè)狀態(tài)需要執(zhí)行的代碼。
那么,知道這個(gè)有什么用呢?第一,它是你充分了解async/await這套機(jī)制的基礎(chǔ),包括與之相關(guān)的同步上下文、執(zhí)行上下文、死鎖問題等,第二,它可以實(shí)現(xiàn)一些特殊的功能。
從上一篇我們知道,OnCompleted中的contination的主要目的是推動(dòng)狀態(tài)機(jī)的執(zhí)行,也就是推動(dòng)異步方法中await后面部分的代碼執(zhí)行。從這里看出,continuation的執(zhí)行是受我們控制的,因此我們可以直接執(zhí)行它,或是等待某個(gè)條件成熟然后執(zhí)行它,我們可以把它放到線程池執(zhí)行,也可以單獨(dú)起一個(gè)線程執(zhí)行。譬如,我們可以讓await后面部分的代碼直接在線程池上執(zhí)行。
public static async Task AwaiterTest() { Console.WriteLine($"是否是線程池線程?{Thread.CurrentThread.IsThreadPoolThread}"); await default (SkipToThreadPoolAwaiter); Console.WriteLine($"是否是線程池線程?{Thread.CurrentThread.IsThreadPoolThread}"); } static void Main(string[] args) { _ = AwaiterTest(); Console.ReadLine(); } public struct SkipToThreadPoolAwaiter : INotifyCompletion { public bool IsCompleted => false; public void GetResult() { Console.WriteLine("調(diào)用GetResult以獲取結(jié)果"); } public void OnCompleted(Action continuation) { Console.WriteLine("調(diào)用OnCompleted,把Await后面部分要執(zhí)行的代碼傳遞過來(傳遞MoveNext,以推動(dòng)狀態(tài)機(jī)流轉(zhuǎn))"); ThreadPool.QueueUserWorkItem(state => { Console.WriteLine("開始執(zhí)行Await后面部分的代碼"); continuation(); Console.WriteLine("后面部分的代碼執(zhí)行完畢"); }); Console.WriteLine("返回調(diào)用線程"); } public SkipToThreadPoolAwaiter GetAwaiter() { Console.WriteLine("獲得Awaiter"); return this; } }
這是一個(gè)控制臺(tái)程序,輸出結(jié)果如下。
是否是線程池線程?False
獲得Awaiter
調(diào)用OnCompleted,把Await后面部分要執(zhí)行的代碼傳遞過來(傳遞MoveNext,以推動(dòng)狀態(tài)機(jī)流轉(zhuǎn))
返回調(diào)用線程
開始執(zhí)行Await后面部分的代碼
調(diào)用GetResult以獲取結(jié)果
是否是線程池線程?True
后面部分的代碼執(zhí)行完畢
特別注意一下,第五步說明可能有點(diǎn)疑惑,怎么第六步不是打印是否是線程池線程?原因是部分awaiter是有返回值的,在執(zhí)行await后面部分的代碼時(shí),會(huì)首先調(diào)用GetResult()以獲取結(jié)果。這對編譯器改造異步方法來說是一個(gè)固定的模式(上篇文章沒有體現(xiàn)這一步)。
把Awaiter改成有返回值嘗試。
public static async Task AwaiterTest() { Console.WriteLine($"是否是線程池線程?{Thread.CurrentThread.IsThreadPoolThread}"); var res = await default (SkipToThreadPoolAwaiter); Console.WriteLine($"結(jié)果是{res}"); } static void Main(string[] args) _ = AwaiterTest(); Console.ReadLine(); public struct SkipToThreadPoolAwaiter : INotifyCompletion public bool IsCompleted => false; public int GetResult() { Console.WriteLine("調(diào)用GetResult以獲取結(jié)果"); return 1; } public void OnCompleted(Action continuation) Console.WriteLine("調(diào)用OnCompleted,把Await后面部分要執(zhí)行的代碼傳遞過來(傳遞MoveNext,以推動(dòng)狀態(tài)機(jī)流轉(zhuǎn))"); ThreadPool.QueueUserWorkItem(state => { Console.WriteLine("開始執(zhí)行Await后面部分的代碼"); continuation(); Console.WriteLine("后面部分的代碼執(zhí)行完畢"); }); Console.WriteLine("返回調(diào)用線程"); public SkipToThreadPoolAwaiter GetAwaiter() Console.WriteLine("獲得Awaiter"); return this;
輸出如下
是否是線程池線程?False
獲得Awaiter
調(diào)用OnCompleted,把Await后面部分要執(zhí)行的代碼傳遞過來(傳遞MoveNext,以推動(dòng)狀態(tài)機(jī)流轉(zhuǎn))
返回調(diào)用線程
開始執(zhí)行Await后面部分的代碼
調(diào)用GetResult以獲取結(jié)果
結(jié)果是1
是否是線程池線程?True
后面部分的代碼執(zhí)行完畢
對照前面的文章來看,相信你應(yīng)該有所得,能解決你部分的疑惑。前面說到,我們可以控制continuation的執(zhí)行,那如果當(dāng)前線程有同步上下文(SychronizationContext),我們是不是可以放到同步上下文中執(zhí)行?TaskAwaiter是會(huì)這么做的,如果你不想它使用同步上下文,你可以在Task實(shí)例上調(diào)用ConfigureAwait(false),它表面后面部分的代碼將不會(huì)使用同步上下文執(zhí)行。
另外說一下Task.Yield()這個(gè)Awaiter,他的行為是捕捉同步上下文,如果有,則會(huì)放到同步上下文中執(zhí)行,如果沒有,則會(huì)放到線程池中執(zhí)行。在窗體程序中,有時(shí)候你打開一個(gè)模態(tài)對話框,會(huì)導(dǎo)致主窗體部分的動(dòng)畫沒有反應(yīng),在模態(tài)對話框關(guān)閉之后,才會(huì)反應(yīng)。原因是模態(tài)對話框阻塞了主窗體的消息循環(huán),也就是阻塞了主線程,如果想讓動(dòng)畫先完成,然后再打開模態(tài)對話框,則可以在打開模態(tài)對話框之前,Await Task.Yield(),這也對應(yīng)了它的意思,讓渡之意。
后面文章還會(huì)說明同步上下文具體是什么、異步代碼中使用同步代碼會(huì)導(dǎo)致死鎖的本質(zhì)原因、如何實(shí)現(xiàn)類似Task的類,并且怎么與Async/await這套機(jī)制搭配使用等知識(shí)。
覺得有收獲的不妨點(diǎn)個(gè)贊,有支持才有動(dòng)力寫出更好的文章。
到此這篇關(guān)于C#異步編程由淺入深(三)細(xì)說Awaiter的文章就介紹到這了,更多相關(guān)C#異步編程Awaiter內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
unity 如何獲取Text組件里text內(nèi)容的長度
這篇文章主要介紹了unity 獲取Text組件里text內(nèi)容的長度操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04使用C#?11的靜態(tài)接口方法改進(jìn)?面向約定?的設(shè)計(jì)方法
我們知道接口是針對契約的定義,但是一直以來它只能定義一組“實(shí)例”的契約,而不能定義類型的契約,因?yàn)槎x在接口中的方法只能是實(shí)例方,這篇文章主要介紹了使用C#?11的靜態(tài)接口方法改進(jìn)面向約定?的設(shè)計(jì),需要的朋友可以參考下2022-12-12C# Process調(diào)用外部程序的實(shí)現(xiàn)
這篇文章主要介紹了C# Process調(diào)用外部程序的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02