一文說通C#中的非同步程式設計補遺

老王Plus發表於2020-08-05

前文寫了關於C#中的非同步程式設計。後臺有無數人在討論,很多人把非同步和多執行緒混了。

文章在這兒:一文說通C#中的非同步程式設計

所以,本文從體系的角度,再寫一下這個非同步程式設計。

一、C#中的非同步程式設計演變

1. 非同步程式設計模型

這是C#中早期的非同步模型,通過IAsyncResult介面來實現。

實現的程式碼大體是這個樣子:

class MyClass
{

    IAsyncResult BeginAction(para ..., AsyncCallback callback, object state);
    EndAction(IAsyncResult async_result);
}

這種方式在一些庫裡還有保留,像FileSteam類裡的BeginReadEndRead方法組,就是這種方式。

程式設計時,不建議用這種方式。

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

二、非同步模型中的“任務”

先來看看任務:TaskTask<T>,這是非同步模型的核心。

這個“任務”,是一種“承諾”,承諾會在稍後完成任務。

它有兩個關鍵字:asyncawait。注意:是await,不是wait。這兒再強調一下,Task.Wait是個同步方法,用在多執行緒中等待。TaskThread的子集,因此繼承了Wait方法,但這個方法不是給非同步用的。

在某些情況下,非同步可以採用多執行緒來實現,這時候,Task.Wait可以用,但這是以多執行緒的身份來使用的,用出問題要查執行緒,而不是非同步。

關於非同步中Taskasyncawait配合的部分,可以去看前一個文章。地址在:一文說通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裡。

這種方式中,非同步被放到了主執行緒以外的新執行緒中執行,換句話說,這個非同步在以多執行緒的方式執行。

在這種模式下,asyncawait的配合,以及對程式執行次序的控制,跟單執行緒模式是完全一樣的。但是要注意,前邊說了,asyncawait是非同步的關鍵字,它不管多執行緒的事,也不會為多執行緒提供任何保護。多執行緒中的併發鎖、資料鎖、上下文切換,還需要以多執行緒的方式另外搞定。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>通過回撥來傳送進度值,引發捕獲並處理。

全文完。

這篇文章是對前一篇文章的補充和擴充套件。所以,要兩篇一起看,才更好。

一文說通C#中的非同步程式設計

 


 

微信公眾號:老王Plus

掃描二維碼,關注個人公眾號,可以第一時間得到最新的個人文章和內容推送

本文版權歸作者所有,轉載請保留此宣告和原文連結

相關文章