一文說通C#中的異步編程補遺

前文寫了關於C#中的異步編程。後臺有無數人在討論,不少人把異步和多線程混了。html

文章在這兒:一文說通C#中的異步編程web

因此,本文從體系的角度,再寫一下這個異步編程。編程

1、C#中的異步編程演變

1. 異步編程模型c#

這是C#中早期的異步模型,經過IAsyncResult接口來實現。微信

實現的代碼大致是這個樣子:多線程

class MyClass
{

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

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

編程時,不建議用這種方式。app

2. 基於事件的異步模型異步

這是C#中間一個過渡時期的異步模型,核心是基於一個或多個事件、事件處理委託的派生類型,是一種使用多線程的模式。async

這個模式在類庫裏,多用在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 ...);
}

咱們所說的異步,包括前文講的異步,所有是基於這個基於任務的異步模型來討論。

在這個模型下,前文說過,異步不是多線程。今天再強調一遍,異步不只不是多線程,同時異步也不必定會使用多線程。

    爲了防止不提供原網址的轉載,特在這裏加上原文連接:http://www.javashuo.com/article/p-actnntok-na.html

2、異步模型中的「任務」

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

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

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

在某些狀況下,異步能夠採用多線程來實現,這時候,Task.Wait能夠用,但這是以多線程的身份來使用的,用出問題要查線程,而不是異步。

關於異步中Taskasyncawait配合的部分,能夠去看前一個文章。地址在:一文說通C#中的異步編程,這兒再也不說了。

3、異步編程的兩種模式

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的內部代碼會佔用線程池資源,並在一個可用的線程上與主線程並行運行。

4、異步的兩個額外狀態

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

掃描二維碼,關注我的公衆號,能夠第一時間獲得最新的我的文章和內容推送

本文版權歸做者全部,轉載請保留此聲明和原文連接