天天看點

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

前文寫了關于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>

通過回調來發送進度值,引發捕獲并處理。

全文完。

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

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

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

微信公衆号:老王Plus

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

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