日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

.net core 非阻塞的异步编程 及 线程调度过程

發布時間:2025/3/8 编程问答 28 如意码农
生活随笔 收集整理的這篇文章主要介紹了 .net core 非阻塞的异步编程 及 线程调度过程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文主要分為三個部分:

1、語法格式

2、線程調度情況

3、編程注意事項

4、練一練

* 閱讀提示 :鼠標懸停在 章節標題 上可見 文章目錄

異步編程(Task Asynchronous Programming,TAP),一種編程模式(Task-based Asynchronous Pattern)。

TAP 是 .NET 中推薦的異步編程模式,基于 Task 和 Task<TResult> 類型,用于表示異步。

異步編程一般應對兩種場景,一是 I/O 綁定,當需要網絡連接(連接數據庫或讀寫到文件系統等)等耗時長的任務;二是 CPU 綁定,需要耗時長的計算。

1、簡單的語法格式

.net 一直在為開發人員簡化和標準化異步的寫法,從 .net 4.5 開始就已經支持使用 aysnc 和 await 的關鍵字。

異步的語法格式如下:

private async Task<TResult> DoSomeStuffAsync(..)

{

..

await ..

..

}

l  關鍵詞 async 本身不具備什么意義,只是裝飾,當方法冠以 async 關鍵詞,方法體內允許使用 await

l  await 是標記需要等待的地方,但其本質并非阻塞線程。

l  “非阻止操作”:指當運行到 await 時,會把當前線程返回到上一級調用者繼續執行,如果沒有上一級調用者,則該線程當場釋放。

非阻止操作

阻止操作

備注

獲取任務返回 await task task.Wait / task.Result

非阻塞:線程遇到 await 時會返回上一層調用者繼續執行,如果沒有上一級調用者,則釋放該線程;

阻塞:線程在等待期間不能執行其他任務,也不釋放線程,硬等

任一任務完成

await Task.WhenAny

Task.WaitAny

所有任務完成

await Task.WhenAll

Task.WaitAll

等待一段時間

await Task.Delay

Thread.Sleep

l  異步方法返回的值總是 Task 的實例,可以是 Task 類型或 Task<TResult>,其中 TResult 是執行的方法的返回類型

l  如果直接拿異步方法的結果,形如 var obj = GetSomethingAsync(),這個 obj 是一個 Task 對象,其中 obj.AsyncStatus,obj.Result 可見執行情況

l  await + 執行異步方法,形如 await GetSomethingAsync() 會得到 TResult 實例

l  直接使用 task.Result 得到的任務結果其狀態是未知的,應該使用 await task,保證任務是 Completed 的

l  一般地,異步方法的名字需要添加后綴 Async,以便于寫代碼的時候區分開同步方法和異步方法

l  等待異步任務的執行過程中,如果其中發生了錯誤,該異步任務的外層 try catch 會捕捉到,它也是一種任務結果

2、異步運行機制,線程調度

觀察以下代碼,思考一下控制臺會輸出什么?

public static async Task Main(string[] args)
{
logMessage("Main <<----");
var task = GetUrlContentLengthAsync();
logMessage("Main ---->>");
await task;
logMessage("ALL COMPLETED");
} static async Task<int> GetUrlContentLengthAsync()
{
logMessage("GetUrlContentLengthAsync start "); using var client = new HttpClient();
Task<string> getStringTask = client.GetStringAsync("https://learn.microsoft.com/dotnet"); // Do some independent work.. var contents = await getStringTask; logMessage("GetUrlContentLengthAsync end ");
return contents.Length;
} static void logMessage(string msg)
{
Console.WriteLine($"{DateTime.Now.ToString("MM-dd HH:mm:ss.fff")} [{Thread.CurrentThread.ManagedThreadId}] {msg}");
}

打印結果:

11-18 18:34:34.563 [1] Main <<----
11-18 18:34:34.632 [1] GetUrlContentLengthAsync start
11-18 18:34:34.810 [1] Main ---->>
11-18 18:34:37.283 [7] GetUrlContentLengthAsync end
11-18 18:34:37.286 [7] ALL COMPLETED

先分析一下 GetUrlContentLengthAsync 這個異步方法,簡單歸納會存在以下步驟:

  1. httpClient.GetStringAsync 發起 HTTP 請求,并立即返回一個未完成的任務。
  2. await 關鍵字會暫停 GetStringAsync 方法的執行,并將控制權返回給調用方。
  3. 任務調度器通過操作系統的通知機制來監聽 HTTP 請求的響應。
  4. 操作系統在后臺監控 I/O 操作的狀態,并在操作完成時通知應用程序。
  5. 任務調度器隨后選擇一個可用的線程來繼續執行 異步方法的剩余部分(await 之后)。

線程運行過程如下:

* HTTP 請求是一個 I/O 操作,操作系統會通過 I/O 完成端口(IOCP)來處理這些操作。

* IOCP 是一種高效的機制,用于處理異步 I/O 操作。它允許操作系統在 I/O 操作完成時通知應用程序。

3、注意事項

如果調用了 異步方法,一定要 await 任務執行結果

反面示例:

public static async Task Main(string[] args)
{
logMessage("Main <<----"); // fault example: if do not wait the result then we will do not know what happened on it
GetSomeStuff(); logMessage("Main ---->>");
}

這個異步任務已經在執行,但是卻沒有后續處理,不知道是成功或是失敗,又或者一直在執行沒有辦法停止,這是危險的。

避免使用 task.Result,應該使用 await 

直接使用 task.Result 的方式拿結果是線程阻塞的方式,也即如果任務還沒完成,那么這個線程將什么都不干,只等待任務完成。
使用 await,而避免使用 xxTask.Result
阻塞線程:如果任務尚未完成,訪問 Result 會阻塞當前線程,直到任務完成。這會導致性能問題,特別是在 UI 線程中使用時,會導致界面卡頓。
死鎖風險:在某些情況下,特別是在同步上下文(如 UI 線程)中,訪問 Result 可能會導致死鎖(任務等待當前線程釋放,而當前線程又在等待任務完成)。
異常處理:直接訪問 Result 可能會忽略任務中的異常。使用 await 可以更好地處理異常。

是否線程安全

異步編程的機制,允許到正在處理一個請求時,同時存在多個線程在處理操作,如果在對同一個對象做寫入操作,這是危險的。

所以并行時的任務最好是沒有關系的。

如果使用鎖,需要考慮是否會導致死鎖的問題。

異步編程可能會增加代碼復雜度,慎重取舍

異步編程并不一定帶來性能提升,畢竟上下文切換也是有開銷的,對于簡單的任務可能一條線做完的方式更合適。

4、練一練

以下代碼有什么問題?

public async Task<ResultResponse> ValidateReceiptAsync(List<Guid> customerIds, ReceiptRequest request, CancellationToken cancellationToken)
{
var tokenTask = _medicalCheckServiceHelper.GetServiceToken(cancellationToken); var medicalProvidersTask = _medicalProviderRepo.GetMedicalProvidersNames(); var customersTask = _customerService.GetByIdsAsync(customerIds); var checkResults = await Task.WhenAll(request.UuidList.Select(uuid => _medicalCheckServiceHelper.GetResultAsync(uuid, tokenTask.Result, cancellationToken))); return await ConsolidateReceiptsResult(medicalProvidersTask.Result, checkResults, await customersTask);
}

思考一下

  • 1. tokenTask.Result 在任務完成之前訪問會導致阻塞,應該在 await tokenTask 之后再訪問。
  • 2. 確認哪些可以并行處理,注意線程安全

這里有 4 個任務,分別是 獲取 token(假作 taskA)、獲取治療廠商(taskB)、獲取用戶信息(taskC)、獲取檢查結果(taskD)
其中,taskD 依賴于 taskA ;現在需要確定 taskA、taskB、taskC 之間的依賴關系。

    • a. 假設真實場景如下:

      • i. taskA 取自于配置中心,也即它會走 HTTP 請求
      • ii. taskD 是一個第三方接口,也即一個 HTTP 請求
      • iii. taskB 和 taskC 取自同一個數據庫,并且它們共用了一個數據庫連接的上下文

那么可能的修改是這樣的:

public async Task<ResultResponse> ValidateReceiptAsync(List<string> customerIds, ReceiptRequest request, CancellationToken cancellationToken)
{
var tokenTask = _medicalCheckServiceHelper.GetServiceToken(cancellationToken);
var medicalProvidersTask = _medicalProviderRepo.GetMedicalProvidersNames(); var token = await tokenTask;
var checkResults = await Task.WhenAll(request.UuidList.Select(uuid => _medicalCheckServiceHelper.GetResultAsync(uuid, token, cancellationToken))); var medicalProviders = await medicalProvidersTask;
var customers = await _customerService.GetByIdsAsync(customerIds); return await ConsolidateReceiptsResult(medicalProviders, checkResults, customers);
}
    • b. 假設真實場景如下:

      • i. taskA 取自于配置中心,也即它會走 HTTP 請求
      • ii. taskD 是一個第三方接口,也即一個 HTTP 請求
      • iii. taskB 和 taskC 取自不同數據庫或不同的 HTTP 請求,它們相互獨立

那么可能的修改是這樣的:

public async Task<ResultResponse> ValidateReceiptAsync(List<string> customerIds, ReceiptRequest request, CancellationToken cancellationToken)
{
var tokenTask = _medicalCheckServiceHelper.GetServiceToken(cancellationToken);
var medicalProvidersTask = _medicalProviderRepo.GetMedicalProvidersNames();
var customersTask = _customerService.GetByIdsAsync(customerIds); var token = await tokenTask;
var checkResultsTask = Task.WhenAll(request.UuidList.Select(uuid => _medicalCheckServiceHelper.GetResultAsync(uuid, token, cancellationToken))); var medicalProviders = await medicalProvidersTask;
var customers = await customersTask;
var checkResults = await checkResultsTask;
return await ConsolidateReceiptsResult(medicalProviders, checkResults, customers);
}

但其實并不盡完善,因為這種寫法的可讀性并沒有那么好,看起來更追求資源優化。
所以說引入了 異步編程 的話,代碼是會變復雜的,每寫一步都需要慎重考慮。

Reference:

[1] The Task Asynchronous Programming (TAP) model with async and await" - C# | Microsoft Learn

[2] Asynchronous programming scenarios - C# | Microsoft Learn

[3] Asynchronous programming - C# | Microsoft Learn

總結

以上是生活随笔為你收集整理的.net core 非阻塞的异步编程 及 线程调度过程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。