本文還處於草稿階段,難免還有錯誤修改改正,邏輯還不是很清晰,筆者會努力完善,長期更新!
[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 接口提供異步行為的舊模型。 在這種模式下,同步操作需要
Begin
和End
方法(例如,BeginWrite
和EndWrite
以實現異步寫入操作)。 不建議新的開發使用此模式。 有關詳細信息,請參閱異步編程模型 (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 將公開 BeginRead
和 EndRead
方法:
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 的對象?
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和博客園,歡迎讀者轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接!請讀者/爬蟲們尊重版權