# C# 中的Task創建指南

  • 2019 年 10 月 3 日
  • 筆記

本文還處於草稿階段,難免還有錯誤修改改正,邏輯還不是很清晰,筆者會努力完善,長期更新!

[0000] 前言

標題起得有些"大",意在集大家的力量,總結出來一份關於Task相對"正確"的知識總結,歡迎讀者提出寶貴意見!本文內容來自於筆者在編碼的時候種種疑問,來自於對非同步編程在作業系統中實際運行過程的好奇。平時使用Task戰戰兢兢,既想提高效率,又怕它不受控制,到處亂來。與其這樣,不如此時此刻一起來了解它神秘的面紗吧!Just do IT.

[0001] 為什麼要編寫非同步程式碼

新型應用廣泛使用文件和網路 I/O。 默認情況下 I/O API 一般會阻塞,導致糟糕的用戶體驗和硬體利用率,除非希望學習和使用富有挑戰的模式。 基於任務的非同步 API 和語言級非同步編程模型改變了這種模型,只需了解幾個新概念就可默認進行非同步執行。

非同步程式碼具有以下特點:

  • 等待 I/O 請求返回的同時,可通過生成處理更多請求的執行緒,處理更多的伺服器請求。
  • 等待 I/O 請求的同時生成 UI 交互執行緒,並通過將長時間運行的工作轉換到其他 CPU 核心,讓 UI 的響應速度更快。
  • 許多較新的 .NET APIs 都是非同步的。
  • 在 .NET 中編寫非同步程式碼很簡單!

來源: https://docs.microsoft.com

[0010] 關於C#中的非同步編程模式

.NET 提供了執行非同步操作的三種模式:

  • 基於任務的非同步模式 (TAP) ,該模式使用單一方法表示非同步操作的開始和完成。 TAP 是在 .NET Framework 4 中引入的。 這是在 .NET 中進行非同步編程的推薦方法。 C# 中的 async 和 await 關鍵詞以及 Visual Basic 中的 Async 和 Await 運算符為 TAP 添加了語言支援。 有關詳細資訊,請參閱基於任務的非同步模式 (TAP)

  • 基於事件的非同步模式 (EAP) ,是提供非同步行為的基於事件的舊模型。 這種模式需要後綴為 Async 的方法,以及一個或多個事件、事件處理程式委託類型和 EventArg 派生類型。 EAP 是在 .NET Framework 2.0 中引入的。 建議新開發中不再使用這種模式。 有關詳細資訊,請參閱基於事件的非同步模式 (EAP)

  • 非同步編程模型 (APM) 模式(也稱為 IAsyncResult 模式),這是使用 IAsyncResult 介面提供非同步行為的舊模型。 在這種模式下,同步操作需要 BeginEnd 方法(例如,BeginWriteEndWrite以實現非同步寫入操作)。 不建議新的開發使用此模式。 有關詳細資訊,請參閱非同步編程模型 (APM)

模式的比較

為了快速比較這三種模式的非同步操作方式,請考慮使用從指定偏移量處起將指定量數據讀取到提供的緩衝區中的Read方法:

public class MyClass  {      public int Read(byte [] buffer, int offset, int count);  }  

此方法對應的 TAP 將公開以下單個 ReadAsync 方法:

public class MyClass  {      public Task<int> ReadAsync(byte [] buffer, int offset, int count);  }  

對應的 EAP 將公開以下類型和成員的集:

public class MyClass  {      public void ReadAsync(byte [] buffer, int offset, int count);      public event ReadCompletedEventHandler ReadCompleted;  }  

對應的 APM 將公開 BeginReadEndRead 方法:

public class MyClass  {      public IAsyncResult BeginRead(          byte [] buffer, int offset, int count,          AsyncCallback callback, object state);      public int EndRead(IAsyncResult asyncResult);  }  

來源: https://docs.microsoft.com

分割線,未完區域——————————–

[0011] 實踐

使用Async

請一路Async,否則會不可控。
網路請求,文件讀寫時系統自帶的Async方法不會創建多執行緒,而是使用完成埠,依靠中斷來實現!
執行緒池中的執行緒分為 WorkerThread 和 CompletionPortThread .
平時我們使用的執行緒是WorkerThread,IO讀寫使用的是CompletionPortThread

1. 創建IO密集型任務

以下程式碼不會創建多個執行緒(WorkerThread),程式碼會在當前執行緒工作,且不會堵塞哦。
執行起來非常類似同步程式, 使用 await RunActionAsync(()=>{});
後,會立即執行程式

public Task RunActionAsync(Action action)  {      TaskCompletionSource<Task> source = new TaskCompletionSource<Task>(TaskCreationOptions.AttachedToParent);      Task<Task> task = source.Task;        try      {          action.Invoke();      }      catch (Exception ex)      {          source.SetException(ex);      }        source.SetResult(Task.CompletedTask);        return task;  }

2. 計算密集型任務

以下程式碼會創建新執行緒(WorkerThread),位於執行緒池,執行緒池默認最小WorkerThread為CPU核心數,CompletionPortThread為1000(實際最小值依實際運行情況而定,可手工修改)
運行時並不會立即執行Action,按照默認執行計劃(TaskScheduler.Default執行,比如用for循環一堆Task.Run(async ()=> {await httpgetAsync(); echo(i); )任務,執行時你會發現i都是最後一個值

await Task.Run(()=>{});

以下程式碼會創建新執行緒(WorkerThread),在不在ThreadPool關鍵在於TaskCreationOptions枚舉,如果為LongRunning,則直接會創建一個非執行緒池的執行緒執行任務,如果不是,則會在執行緒池裡尋找執行緒,如果沒有,會在執行緒池裡新申請執行緒(創建一個耗時一秒),執行任務。
會立即執行Action

Task.Factory.StartNew(_ =>  {      action.Invoke();  },      null,      CancellationToken.None,      TaskCreationOptions.LongRunning,      TaskScheduler.Default)

3.一些坑

以下程式碼.NET Core不支援哦,請使用Task.Factory.StartNew代替

Task.Factory.FromAsync(      new Func<AsyncCallback, object, IAsyncResult>((cb, obj) => action.BeginInvoke(biz, cb, obj)),      new Action<IAsyncResult>(ar => action.EndInvoke(ar)), null)

參考

The danger of TaskCompletionSource class

了解 .NET 的默認 TaskScheduler 和執行緒池(ThreadPool)設置,避免讓 Task.Run 的性能急劇降低

.NET 中小心嵌套等待的 Task,它可能會耗盡你執行緒池的現有資源,出現類似死鎖的情況

.NET 中使用 TaskCompletionSource 作為執行緒同步互斥或非同步操作的事件

定義一組抽象的 Awaiter 的實現介面,你下次寫自己的 await 可等待對象時將更加方便

.NET 除了用 Task 之外,如何自己寫一個可以 await 的對象?

.NET 中什麼樣的類是可使用 await 非同步等待的?

Asynchronous I/O in C#: I/O Completion Ports

Asynchronous I/O in C#: I/O Completion Ports

Migrating Delegate.BeginInvoke Calls for .NET Core

聲明

本文採用知識共享署名-非商業性使用-相同方式共享 2.5 中國大陸許可協議進行許可,發表在CSDN部落格園,歡迎讀者轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接!請讀者/爬蟲們尊重版權