在上一章(Asp.Net Core 輕鬆學-多線程之Task快速上手)[https://www.cnblogs.com/viter/p/10201228.html]文章中,介紹了使用Task的各種常用場景,但是感覺有部分內容還沒有完善,在這裡補充一下。 ...
前言
在上一章 Asp.Net Core 輕鬆學-多線程之Task快速上手 文章中,介紹了使用Task的各種常用場景,但是感覺有部分內容還沒有完善,在這裡補充一下。
1. 任務的等待
在使用 Task 進行基於隊列的非同步任務(TAP)的時候,對於剛入門的同學來說,只是簡單的瞭解了使用 Task 可以在後臺處理非同步任務,但是對於阻塞調用可能還有有一些不太明白,非同步任務預設是不阻塞的執行過程,當一個 Task 被創建出來的時候,並沒有被壓入隊列中,而是開始執行的時候,才會進入隊列中;執行一個非同步任務可以設置等待
1.1 使用 await 進行等待任務完成
[HttpGet]
public async Task<string> Get()
{
string result = string.Empty;
await Task.Run(() =>
{
result = "Hello World!";
});
return result;
}
上面的非同步方法 Get() 內部執行了一個 Task,為了保持方法在返回的時候能夠給變數 result 設置值,這裡使用了 await 等待任務完成,在任務未完成前,即 result 未被設置值 Hello World! 前,該介面將一直阻塞,直到任務完成。
1.1 使用 Wait()
[HttpGet("WaitTest")]
public string WaitTest(int id)
{
string result = string.Empty;
var waitTask = Task.Run(() =>
{
result = "Hello World!";
});
waitTask.Wait(TimeSpan.FromSeconds(5));
return result;
}
上面的代碼定義了一個 TAP ,waitTask 使用了 Wait() 方法進行等待並傳入一個時間,表示等待 5 秒中後退出阻塞過程,
1.2 使用 CancellationToken 方法
[HttpGet("WaitToken")]
public string WaitToken(int id)
{
var result = string.Empty;
CancellationTokenSource cts = new CancellationTokenSource();
var taskToken = Task.Run(() =>
{
cts.CancelAfter(TimeSpan.FromSeconds(1));
Task.Delay(2000).Wait();
result = "Hello World!";
});
taskToken.Wait(cts.Token);
return result;
}
上面的任務 taskToken ,則使用了取消令牌,當令牌沒有收到取消通知的時候,該任務將一直等待, taskToken 任務內部指示取消令牌 1 秒後取消,同時,任務內部使用 Task.Delay 阻塞 2 秒,這很特別,這種設置使得 taskToken 任務將引發任務取消的異常而導致無法給 result 變數進行值設置,如果你對取消令牌不太瞭解,建議閱讀我之前的文章 Asp.Net Core 輕鬆學-多線程之取消令牌
2. 同步方法中的非同步任務
在同步方法中,我們可以非常容易的創建一個 Task 任務,特別是 .Net Core 提供了 Task 這麼方便的使用方式的情況下,在某些場景下,就會出現一些意想不到的問題,我的忠告是:在使用 TAP 的時候,儘可能的使用可等待的任務,特別是需要註意不要在 TAP 中運行一些耗時較長的任務,比如批量處理數據、事務過程等等,如果真的是需要有這一類型的任務需要使用 Task 進行處理,那麼我的建議是,將這些任務縮小,然後儘快的結束,比如,你可以使用消息隊列來執行批量處理數據,而 Task 的任務則縮小至把處理條件丟到消息隊列中,這樣的解耦才是正確的選擇,長時間運行於後臺的 TAP,常常導致許多問題,比如消息冒泡、服務重啟,都有可能會中斷 TAP 線程,要知道,所有的 TAP 都是後臺線程,當主進程退出的時候,後臺線程也將被清理
3. 手動排隊任務
在 TheadPool 內部,提供了一個排隊的方法,當線程池資源可用後,將會自動的執行該隊列,這樣做的好處顯而易見,就是你可以通過定義一系列的任務,然後等待線程池去按順序處理它,這個排隊的過程本質上就是隊列
3.1 手動排隊任務
[HttpGet("TaskQueue")]
public bool TaskQueue()
{
var inQueues = ThreadPool.QueueUserWorkItem(ThreadProc);
return inQueues;
}
private void ThreadProc(Object stateInfo)
{
Console.WriteLine("此任務來自線程池隊列執行");
}
上面的代碼中,在 TaskQueue() 內部使用 ThreadPool.QueueUserWorkItem() 將方法 ThreadProc(Object stateInfo) 壓入隊列中,並返回一個值:inQueues ,該值指示任務壓入隊列是否成功,在 .Net Core 中,ThreadPool.QueueUserWorkItem() 提供了 3 個方法重載,可以按需使用;使用重載方法,甚至可以在壓入任務的時候傳入參數調用,類似下麵的代碼
3.2 在排隊任務時傳遞參數
[HttpGet("TaskQueue")]
public bool TaskQueue()
{
var inQueues = ThreadPool.QueueUserWorkItem(ThreadProc);
var inQueuesSecond = ThreadPool.QueueUserWorkItem(ThreadProc, "這是一條測試消息");
return inQueues;
}
private void ThreadProc(Object stateInfo)
{
Console.WriteLine(stateInfo);
}
如果業務需要,該參數還可傳入實體對象
4. 混合方法(Hybrid Approach)
使用混合方法執行 TAP 的好處是,可以隱藏業務實現細節,提供統一調用入口
4.1 定義混合方法
[HttpGet("HyBrid")]
public Task<string> HyBrid([FromQuery]string value)
{
return HyBridInternal(value);
}
private async Task<string> HyBridInternal(string value)
{
await Task.Run(() =>
{
Console.WriteLine(value);
});
return "Your Input:" + value;
}
混合方法,不要被它的名頭唬住,你可以把它看成一個方法重載,這樣做的好處是,當發生異常是,你可以快速的定位到出現異常的方法,而不是任務
結束語
本文的內容只是上一篇文章的補充,所以這裡就不在放入執行結果,但是示例代碼還是一樣的奉上
示例代碼下載
https://files.cnblogs.com/files/viter/Ron.TaskSecond.zip