一文說(shuō)通C#中的異步編程補(bǔ)遺
前文寫(xiě)了關(guān)于C#中的異步編程。后臺(tái)有無(wú)數(shù)人在討論,很多人把異步和多線程混了。
文章在這兒:一文說(shuō)通C#中的異步編程
所以,本文從體系的角度,再寫(xiě)一下這個(gè)異步編程。
?
一、C#中的異步編程演變
1. 異步編程模型
這是C#中早期的異步模型,通過(guò)IAsyncResult接口來(lái)實(shí)現(xiàn)。
實(shí)現(xiàn)的代碼大體是這個(gè)樣子:
class?MyClass
{
????IAsyncResult?BeginAction(para?...,?AsyncCallback?callback,?object?state);
????T?EndAction(IAsyncResult?async_result);
}
這種方式在一些庫(kù)里還有保留,像FileSteam類里的BeginRead和EndRead方法組,就是這種方式。
編程時(shí),不建議用這種方式。
2. 基于事件的異步模型
這是C#中間一個(gè)過(guò)渡時(shí)期的異步模型,核心是基于一個(gè)或多個(gè)事件、事件處理委托的派生類型,是一種使用多線程的模式。
這個(gè)模式在類庫(kù)里,多用在Winform/WPF中的組件的事件處理,你可以隨便拿一個(gè)Framework 4.5以前的組件去研究,大多數(shù)都是這種方式。
這種方式的實(shí)現(xiàn)大體是這個(gè)樣子:
class?MyClass
{
??void?ActionAsync(para?...);
??event?ActionCompletedEventHandler?action_completed;
}
這種方式使用多線程,所以,它具有多線程的全部特點(diǎn)和要求。
從微軟的建議來(lái)看,F(xiàn)ramework 4.5以后,并不推薦使用這種模式。
3. 基于任務(wù)的異步模型
這種異步模型從Framework 4.0以后引入,使用單一方法來(lái)表示異步的開(kāi)始和完成。這是目前推薦的異步開(kāi)發(fā)方式。在上個(gè)文章中的異步模式,就是這個(gè)方式。
這個(gè)方式的代碼實(shí)現(xiàn)是這樣的:
class?MyClass
{
??Task?ActionAsync(para?...);
}
?
我們所說(shuō)的異步,包括前文講的異步,全部是基于這個(gè)基于任務(wù)的異步模型來(lái)討論。
在這個(gè)模型下,前文說(shuō)過(guò),異步不是多線程。今天再?gòu)?qiáng)調(diào)一遍,異步不僅不是多線程,同時(shí)異步也不一定會(huì)使用多線程。
二、異步模型中的“任務(wù)”
先來(lái)看看任務(wù):Task和Task,這是異步模型的核心。
這個(gè)“任務(wù)”,是一種“承諾”,承諾會(huì)在稍后完成任務(wù)。
它有兩個(gè)關(guān)鍵字:async和await。注意:是await,不是wait。這兒再?gòu)?qiáng)調(diào)一下,Task.Wait是個(gè)同步方法,用在多線程中等待。Task是Thread的子集,因此繼承了Wait方法,但這個(gè)方法不是給異步用的。
在某些情況下,異步可以采用多線程來(lái)實(shí)現(xiàn),這時(shí)候,Task.Wait可以用,但這是以多線程的身份來(lái)使用的,用出問(wèn)題要查線程,而不是異步。
關(guān)于異步中Task和async、await配合的部分,可以去看前一個(gè)文章。地址在:一文說(shuō)通C#中的異步編程,這兒不再說(shuō)了。
三、異步編程的兩種模式
1. 單線程模式
先看代碼:
Task<string>?GetHtmlAsync()
{
??var?client?=?new?HttpClient();
??var?gettask?=?client.GetStringAsync("https://home.cnblogs.com/u/tiger-wang");
??return?await?gettask;
}
這種模式下,這個(gè)異步工作于單線程狀態(tài)。代碼雖然返回一個(gè)任務(wù)Task,在這個(gè)任務(wù)依然在主線程中,并沒(méi)有生成一個(gè)新的線程。換句話說(shuō),這種方式不額外占用線程池資源,也不需要考慮多線程開(kāi)發(fā)中線程鎖定、數(shù)據(jù)一致性等問(wèn)題。
因?yàn)榫€程沒(méi)有切換,所以也不存在上下文切換的問(wèn)題。
2. 多線程模式
既然Task派生自Thread,當(dāng)然也可以用多線程來(lái)實(shí)現(xiàn)異步。
看代碼:
Task<string>?GetHtmlAsync()
{
??var?gettask?=?Task.Run(()?=>?{
????var?client?=?new?HttpClient();
????return?client.GetStringAsync("https://home.cnblogs.com/u/tiger-wang");
??});
??return?await?gettask;
}
對(duì)方上一段代碼,把調(diào)用client.GetStringAsync的部分放到了Task.Run里。
這種方式中,異步被放到了主線程以外的新線程中執(zhí)行,換句話說(shuō),這個(gè)異步在以多線程的方式執(zhí)行。
在這種模式下,async和await的配合,以及對(duì)程序執(zhí)行次序的控制,跟單線程模式是完全一樣的。但是要注意,前邊說(shuō)了,async和await是異步的關(guān)鍵字,它不管多線程的事,也不會(huì)為多線程提供任何保護(hù)。多線程中的并發(fā)鎖、數(shù)據(jù)鎖、上下文切換,還需要以多線程的方式另外搞定。Task.Run的內(nèi)部代碼會(huì)占用線程池資源,并在一個(gè)可用的線程上與主線程并行運(yùn)行。
四、異步的兩個(gè)額外狀態(tài)
1. 取消
異步針對(duì)的是需要消耗長(zhǎng)時(shí)間運(yùn)行的工作。在工作過(guò)程中,如果需要,我們可以取消異步的運(yùn)行。系統(tǒng)提供了一個(gè)類CancellationToken來(lái)處理這個(gè)工作。
定義方式:
Task?ActionAsync(para?...,?CancellationToken?cancellationtoken);
調(diào)用方式:
CancellationTokenSource?source?=?new?CancellationTokenSource();
CancellationToken?cancel_token?=?source.Token;
await?ActionAsync(para,?cancel_token);
需要取消時(shí):
source.Cancel();
就可以了。
在做API時(shí),異步中加個(gè)CancellationToken,是基本的代碼禮節(jié)。
2. 進(jìn)度
長(zhǎng)時(shí)間運(yùn)行,如果能給出個(gè)進(jìn)度也不錯(cuò)。
定義方式:
Task?ActionAsync(para?...,?IProgress?progress);
其中,T是需要返回的進(jìn)度值,可以是各種需要的類型。
當(dāng)然,我們需要實(shí)現(xiàn)IProgress
public?class?Progress?: ?IProgress??
{??
????public?Progress();??
????public?Progress(Action?handler) ;??
????protected?virtual?void?OnReport(T?value);??
????public?event?EventHandler?ProgressChanged;??
}??
IProgress通過(guò)回調(diào)來(lái)發(fā)送進(jìn)度值,引發(fā)捕獲并處理。
?
全文完。
?
這篇文章是對(duì)前一篇文章的補(bǔ)充和擴(kuò)展。所以,要兩篇一起看,才更好。
喜歡就來(lái)個(gè)三連,讓更多人因你而受益
