您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關C#中如何實現異步編程,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
在一個被async修飾了的異步方法里,如果沒有遇到await,你的代碼將一直在調用線程上。在UI應用程序里,比如ASP.NET或者WinForm程序里,你的代碼會在ASP.NET工作線程或WinForm工作線程上運行。
我們來看一下以下范例
1: public async Task GetResultAsync()
2: {
3: Console.WriteLine();
4:
5: User user = this.GetUserAsync();
6:
7: //call other code
8:
9: return Task.CompletedTask;
10: }
以上范例里,我們在一個異步方法里調用了另一個異步方法,但是我們并沒有使用await,這段代碼依然在原始調用線程上執行,此時這個方法只是扮演了一個傳播異步的作用。
當我們在UI線程上如此編程的時候,代碼在UI線程是執行,在沒有執行結束之前,頁面是沒有響應的。所以如果頁面長時間沒有響應,未必是異步導致的,可能會有其他原因,需要綜合考慮,可以借助性能分析器來查看影響系統的原因在哪里。
代碼到達await后,到底是哪一個線程在執行異步操作呢。
我們以ASP.NET為例,對于網絡請求之類的操作,此時沒有線程在執行異步操作,他們都被阻塞了,正在等待操作完成。但是如果使用了Task.Run,那么執行該任務時就要用到線程池里的線程了。
那么問題來了,我們在編寫異步方法的時候,確確實實可以看到這個方法被執行了,肯定有線程執行才行啊。
對的,確實需要線程來執行,這個線程我們把它稱之為是IO完成端口線程。此線程等待網絡請求完成,同時它在所有網絡請求之間共享。當網絡請求完成時,操作系統中的中斷處理程序會以Job方式添加到IO完成端口的隊列中。在請求發起后,響應返回前,它們需要依次由單個IO完成端口處理。
實際上,一般情況下只有少量IO完成端口線程,以充分利用多個CPU核心。需要注意的是,無論當前有多少個請求,我們的線程數量都是固定的。
參考以下運行圖
我在異步編程(一)這邊文章里,有講到SynchronizationContext這個類,它是.NET框架提供的類,可以在特定類型的線程中運行代碼。
.NET使用各種SynchronizationContext,常見的有ASP.NET、WinForms和WPF使用的UI線程上下文。SynchronizationContext的實例本身并沒有特殊的地方,其實例指向的是其子類,具有靜態成員,可以用于讀取和控制當前的SynchronizationContext。
當前SynchronizationContext是當前線程的屬性。在一個特定線程所運行到的任意的地方,都能夠獲取當前的SynchronizationContext并存儲它,并且可以使用SynchronizationContext,在所啟動的這個特定線程上運行代碼。綜上所述,我們并不需要知道代碼在哪個線程上啟動,只需要使用到SynchronizationContext,我們就可以返回到啟動線程。
SynchronizationContext的重要方法是POST,它可以使委托在正確的上下文中運行。
某些SynchronizationContext封裝單個線程,如UI線程。有些線程封裝了特定類型的線程,例如線程池,但可以選擇將委托發送到其中的任何一個線程。有些不會更改代碼運行在哪個線程上,而只用于監視,如ASP.NET SynchronizationContext。
到這個地方,我們就需要了解一個問題了。在await之前,我們的代碼是在調用線程上運行,那么await之后,恢復方法時到了哪個線程上了?
實際上,大多數情況下,await后的代碼也由調用線程運行,盡管調用線程可能在等待期間做了其他事情。C#使用SynchronizationContext來完成此操作。當等待任務完成時,當前的同步上下文被存儲為暫停方法的一部分。然后,當方法恢復時,await關鍵字的基礎結構使用POST在捕獲的同步上下文上恢復該方法。
既然有大多數情況,那么肯定也有小眾情況吧,以下情況可以在不同的線程上運行
SynchronizationContext具有多個線程,如線程池
SynchronizationContext不是真正切換線程的上下文
到達等待時,沒有當前的同步上下文,例如在控制臺應用程序中。
將任務配置為不使用同步上下文來恢復
注意:
對于UI應用程序來說,在同一線程上恢復是最重要的,我們等待之后安全的操作UI。
以WinForm為例,我們設計一個按鈕,用于下載我們喜歡的小圖標。用戶點擊按鈕之后,UI線程啟動,并會執行響應的操作,以下圖片展示了一個異步操作的流程,以及期間UI線程與IO線程是如何切換的
1、用戶單擊該按鈕,事件處理程序GetButton_OnClick開始排隊等待運行。
2、用戶界面線程執行GetButton_OnClick的前半部分,包括對GetFaviconAsync的調用。
3、UI線程繼續進入GetFaviconAsync并執行其前半部分,包括對DownloadDataTaskAsync的調用。
4、UI線程繼續進入DownloadDataTaskAsync,它啟動下載并返回任務。
5、UI線程離開DownloadDataTaskAsync,并返回GgetFaviconAsync處的await。
6、當前的UI線程捕獲到了SynchronizationContext。
7、GetFaviconAsyncy因為有await的標識,會等待,當DownloadDataTaskAsync完成后GetFaviconAsyncy便會使用捕獲到的SynchronizationContext恢復。
8、用戶線程離開GetFaviconAsync,并返回一個任務,并運行到GetButton_OnClick中的await。
9、類似地,GetButton_OnClick被等待暫停。
10、用戶線程離開GetButton_OnClick,可能會用于處理其他操作。【此時,我們正在等待圖標下載。可能需要幾秒鐘。注意,UI線程可以自由處理其他用戶操作,而IO完成端口線程尚未涉及到。操作期間阻塞的線程總數為零。】
11、下載完成,因此IO完成端口在DownloadDataTaskAsync中對邏輯進行排隊處理。
12、IO完成端口線程將把DownloadDataTaskAsync返回的任務設置為完成。
13、IO完成端口線程在任務內部運行代碼并處理完成,并會調用捕獲到的同步上下文(UI線程)上的POST以繼續運行接下來的代碼。
14、IO完成端口線程被釋放并可能在其他IO上工作。
15、用戶界面線程找到POST指令,并繼續執行GetFaviconAsync的后半部分,直到結束。
16、當UI線程離開GetFaviconAsync時,它會將GetFaviconAsync返回的任務設置為完成。
17、在這個運行點里,當前的同步上下文與捕獲的上下文相同,因而無需用到POST,UI線程也會繼續同步進行。【此邏輯在WPF中是無效的,因為WPF經常創建新的SynchronizationContext對象。盡管它們是等效的,這使得TPL認為它需要重新POST。】
18、用戶線程繼續運行GetButton_OnClick的后半部分,直到結束。
同步上下文的每個實現都是以不同的方式執行POST的,這是非常消耗性能的事情。為了避免這種開銷,.NET內部也是有自己的優化機制的,它會在捕獲的SynchronizationContext與任務完成時的當前上下文相同時,不使用POST。很有意思的是,如果你使用調試器查看這種情況,會發現調用堆棧是顛倒的。
但是,當同步上下文不同時,這就需要用到系統開銷了。在性能關鍵的代碼中或者某個代碼庫中,如果我們并不不關心使用到了哪個線程,這個時候我們也可以通過自己的手動操作來避開這種開銷。
在等待任務之前調用ConfigureaWait來完成。這樣就不會恢復到原始同步上下文。
1: byte[] bytes = await httpClient.PostAsJsonAsync(url,data).ConfigureAwait(false).ReadAsStreamAsync();
不過,ConfigureAwait并不是嚴格的指令,它是.NET設計的一個標識,用來告訴運行時我們不介意方法在哪個線程上運行。如果該線程不重要(線程池線程),它將會繼續執行代碼。如果是很重要的線程,.NET會通過自身機制將線程釋放,讓它來做其他事情,而方法也將在線程池中恢復。.NET使用線程的當前的SynchronizationContext來判斷它是否重要。
前文有說過,本文再提一次,在同步代碼中運行異步代碼,可能有隱藏的問題。Task有一個Result屬性,該屬性阻止等待任務完成。如以下代碼:
1: var result = GetUserAsync().Result;
但是如果在只有一個線程(如UI線程)的SynchronizationContext使用就會發生死鎖現象。解決問題的方法就是,我們可以使用線程池線程來解決這個問題。如以下代碼:
1: var result = Task.Run(() =>GetUserAsync()).Result;
上述就是小編為大家分享的C#中如何實現異步編程了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。