前文寫了關於C#中的異步編程。后台有無數人在討論,很多人把異步和多線程混了。
文章在這兒:一文說通C#中的異步編程
所以,本文從體系的角度,再寫一下這個異步編程。
一、C#中的異步編程演變
1. 異步編程模型
這是C#中早期的異步模型,通過IAsyncResult
接口來實現。
實現的代碼大體是這個樣子:
class MyClass
{
IAsyncResult BeginAction(para ..., AsyncCallback callback, object state);
T EndAction(IAsyncResult async_result);
}
這種方式在一些庫里還有保留,像FileSteam
類里的BeginRead
和EndRead
方法組,就是這種方式。
編程時,不建議用這種方式。
2. 基於事件的異步模型
這是C#中間一個過渡時期的異步模型,核心是基於一個或多個事件、事件處理委托的派生類型,是一種使用多線程的模式。
這個模式在類庫里,多用在Winform/WPF中的組件的事件處理,你可以隨便拿一個Framework 4.5以前的組件去研究,大多數都是這種方式。
這種方式的實現大體是這個樣子:
class MyClass
{
void ActionAsync(para ...);
event ActionCompletedEventHandler action_completed;
}
這種方式使用多線程,所以,它具有多線程的全部特點和要求。
從微軟的建議來看,Framework 4.5以后,並不推薦使用這種模式。
3. 基於任務的異步模型
這種異步模型從Framework 4.0以后引入,使用單一方法來表示異步的開始和完成。這是目前推薦的異步開發方式。在上個文章中的異步模式,就是這個方式。
這個方式的代碼實現是這樣的:
class MyClass
{
Task<T> ActionAsync(para ...);
}
我們所說的異步,包括前文講的異步,全部是基於這個基於任務的異步模型來討論。
在這個模型下,前文說過,異步不是多線程。今天再強調一遍,異步不僅不是多線程,同時異步也不一定會使用多線程。
為了防止不提供原網址的轉載,特在這里加上原文鏈接:https://www.cnblogs.com/tiger-wang/p/13428372.html
二、異步模型中的“任務”
先來看看任務:Task
和Task<T>
,這是異步模型的核心。
這個“任務”,是一種“承諾”,承諾會在稍后完成任務。
它有兩個關鍵字:async
和await
。注意:是await
,不是wait
。這兒再強調一下,Task.Wait
是個同步方法,用在多線程中等待。Task
是Thread
的子集,因此繼承了Wait
方法,但這個方法不是給異步用的。
在某些情況下,異步可以采用多線程來實現,這時候,Task.Wait
可以用,但這是以多線程的身份來使用的,用出問題要查線程,而不是異步。
關於異步中Task
和async
、await
配合的部分,可以去看前一個文章。地址在:一文說通C#中的異步編程,這兒不再說了。
三、異步編程的兩種模式
1. 單線程模式
先看代碼:
Task<string> GetHtmlAsync()
{
var client = new HttpClient();
var gettask = client.GetStringAsync("https://home.cnblogs.com/u/tiger-wang");
return await gettask;
}
這種模式下,這個異步工作於單線程狀態。代碼雖然返回一個任務Task<T>
,在這個任務依然在主線程中,並沒有生成一個新的線程。換句話說,這種方式不額外占用線程池資源,也不需要考慮多線程開發中線程鎖定、數據一致性等問題。
因為線程沒有切換,所以也不存在上下文切換的問題。
2. 多線程模式
既然Task
派生自Thread
,當然也可以用多線程來實現異步。
看代碼:
Task<string> GetHtmlAsync()
{
var gettask = Task.Run(() => {
var client = new HttpClient();
return client.GetStringAsync("https://home.cnblogs.com/u/tiger-wang");
});
return await gettask;
}
對方上一段代碼,把調用client.GetStringAsync
的部分放到了Task.Run
里。
這種方式中,異步被放到了主線程以外的新線程中執行,換句話說,這個異步在以多線程的方式執行。
在這種模式下,async
和await
的配合,以及對程序執行次序的控制,跟單線程模式是完全一樣的。但是要注意,前邊說了,async
和await
是異步的關鍵字,它不管多線程的事,也不會為多線程提供任何保護。多線程中的並發鎖、數據鎖、上下文切換,還需要以多線程的方式另外搞定。Task.Run
的內部代碼會占用線程池資源,並在一個可用的線程上與主線程並行運行。
四、異步的兩個額外狀態
1. 取消
異步針對的是需要消耗長時間運行的工作。在工作過程中,如果需要,我們可以取消異步的運行。系統提供了一個類CancellationToken
來處理這個工作。
定義方式:
Task<T> ActionAsync(para ..., CancellationToken cancellationtoken);
調用方式:
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken cancel_token = source.Token;
await ActionAsync(para, cancel_token);
需要取消時:
source.Cancel();
就可以了。
在做API時,異步中加個CancellationToken
,是基本的代碼禮節。
2. 進度
長時間運行,如果能給出個進度也不錯。
定義方式:
Task<T> ActionAsync(para ..., IProgress<T> progress);
其中,T是需要返回的進度值,可以是各種需要的類型。
當然,我們需要實現IProgress:
public class Progress<T> : IProgress<T>
{
public Progress();
public Progress(Action<T> handler);
protected virtual void OnReport(T value);
public event EventHandler<T> ProgressChanged;
}
IProgress<T>
通過回調來發送進度值,引發捕獲並處理。
全文完。
這篇文章是對前一篇文章的補充和擴展。所以,要兩篇一起看,才更好。
![]() |
微信公眾號:老王Plus 掃描二維碼,關注個人公眾號,可以第一時間得到最新的個人文章和內容推送 本文版權歸作者所有,轉載請保留此聲明和原文鏈接 |