【C#】掌握并發利器:深入理解 .NET 中的 Task.WhenAll

在現代 .NET 應用程序開發中,異步編程(Asynchronous Programming)已成為提升性能、改善響應能力和充分利用多核處理器的關鍵技術。asyncawait 關鍵字極大地簡化了異步代碼的編寫,而 Task 類則是這一模型的核心。在處理多個并發操作時,Task.WhenAll 方法是一個強大且常用的工具。本文將深入探討 Task.WhenAll 的工作原理、使用場景、最佳實踐以及需要注意的陷阱。

什么是 Task.WhenAll

Task.WhenAllTask 類提供的一個靜態方法,用于等待多個任務(TaskTask<T>)全部完成。它接收一個任務集合(如 IEnumerable<Task>Task[]),并返回一個新的 TaskTask<TResult[]>。當傳入的任務全部成功完成時,返回的任務才會完成。如果其中任何一個任務因異常而失敗,返回的任務也會以異常狀態完成。

基本語法

// 等待多個無返回值的任務完成
Task WhenAll(params Task[] tasks);
Task WhenAll(IEnumerable<Task> tasks);// 等待多個有返回值的任務完成
Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks);
Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks);

為什么需要 Task.WhenAll

在沒有 Task.WhenAll 的情況下,我們可能會這樣處理多個異步操作:

// ? 錯誤方式:順序執行,效率低下
var result1 = await GetDataAsync(url1);
var result2 = await GetDataAsync(url2);
var result3 = await GetDataAsync(url3);
// 所有操作是串行的,總耗時約等于各操作耗時之和

或者使用 Task.WhenAny,但它只等待第一個完成的任務,無法滿足“全部完成”的需求。

Task.WhenAll 允許我們并發地啟動所有操作,然后等待它們全部結束,從而顯著減少總執行時間。

? 正確方式:使用 Task.WhenAll

// 啟動所有任務(并發)
var task1 = GetDataAsync(url1);
var task2 = GetDataAsync(url2);
var task3 = GetDataAsync(url3);// 等待所有任務完成
await Task.WhenAll(task1, task2, task3);// 所有任務都已完成,可以安全地獲取結果
var result1 = await task1; // 不會阻塞,因為任務已結束
var result2 = await task2;
var result3 = await task3;

或者更簡潔地:

var tasks = new[]
{GetDataAsync(url1),GetDataAsync(url2),GetDataAsync(url3)
};await Task.WhenAll(tasks);
// 結果按任務在數組中的順序排列
var results = await Task.WhenAll(tasks); // 對于有返回值的任務

核心優勢

  1. 性能提升:通過并發執行,總耗時通常接近于最慢的那個任務的耗時,而不是所有任務耗時之和。
  2. 代碼簡潔:避免了復雜的 Task.ContinueWith 鏈或手動管理多個 TaskCompletionSource
  3. 異常處理集中:所有任務的異常都可以在 await Task.WhenAll(...) 處被捕獲(通常包裝在 AggregateException 中)。
  4. 結果聚合:對于 Task<T>WhenAll 直接返回一個包含所有結果的數組,順序與輸入任務一致。

重要注意事項與最佳實踐

1. 任務必須已經啟動

Task.WhenAll 等待的是已經啟動的任務。確保你在調用 Task.WhenAll 之前已經啟動了所有任務(即調用了異步方法但沒有 await 它)。

// ? 正確:任務已啟動
var task1 = SomeAsyncOperation(); // 啟動任務
var task2 = AnotherAsyncOperation(); // 啟動任務
await Task.WhenAll(task1, task2);// ? 錯誤:任務未啟動(SomeAsyncOperation 返回的是 Task,但未執行)
// await Task.WhenAll(SomeAsyncOperation(), AnotherAsyncOperation()); 
// 這行代碼本身會啟動任務,但寫法不直觀,建議分開寫。

2. 異常處理

Task.WhenAll 返回的任務在任何一個輸入任務失敗時都會失敗。異常通常被包裝在 AggregateException 中。推薦使用 try-catch 塊來處理:

try
{await Task.WhenAll(task1, task2, task3);
}
catch (Exception ex)
{// ex 可能是 AggregateException 或單個異常(.NET 4.5+ 有時會扁平化)// 檢查 ex.InnerException 或 ex.Flatten().InnerExceptions 來獲取具體異常Console.WriteLine($"一個或多個任務失敗: {ex.Message}");// 可以遍歷所有任務檢查其狀態foreach (var task in new[] { task1, task2, task3 }){if (task.IsFaulted){Console.WriteLine($"任務 {task.Id} 失敗: {task.Exception?.InnerException?.Message}");}}
}

3. 性能考量:避免不必要的 Task.WhenAll

如果任務數量很少(比如1-2個),直接 await 每個任務可能更清晰。Task.WhenAll 在處理多個(例如3個以上)獨立任務時優勢最明顯。

4. 與 Task.WhenAny 的區別

  • Task.WhenAll: 等待所有任務完成。
  • Task.WhenAny: 等待任何一個任務完成。適用于“競態”場景,比如從多個數據源獲取數據,取最快返回的結果。

5. 內存與資源管理

啟動大量并發任務可能會耗盡系統資源(如線程池線程、網絡連接、文件句柄)。考慮使用 SemaphoreSlimParallel.ForEachAsync (C# 11+) 來限制并發度。

后續其他文章再展開講解(這里先挖個坑~~~~,可私信我填坑,我怕忘記了。)

6. 創建一個帶有默認值的已完成任務!

有的時候,在進入其他分支的時候,某個task可能是null,這樣Task.WhenAll等待的時候就會報錯,我們需要創建一個帶有默認值的已完成任務.
比如:

Task task3 = Task.FromResult(default(bool))
  1. default(bool)
    default 是 C# 中的一個關鍵字,用于獲取類型的默認值。
    對于值類型 bool,其默認值是 false。
    所以,default(bool) 等價于 false。
  2. Task.FromResult(T result)
    這是 Task 類的一個靜態方法。
    它接收一個類型為 T 的參數 result。
    它返回一個 Task 對象,這個對象的狀態是已完成(RanToCompletion),并且其 Result 屬性的值就是傳入的 result。
    這個方法非常高效,因為它不會創建一個新的線程或啟動任何異步工作;它只是包裝一個已經存在的值到一個 Task 的殼子里。
  3. Task.FromResult(default(bool))
    將兩者結合起來,Task.FromResult(default(bool)) 創建并返回一個 Task。
    這個 Task 立即完成。
    當你 await 這個任務時,你會立即得到 false。

7 混合處理無返回值與有返回值的任務

當我們需要同時等待一個無返回值的 Task 和一個有返回值的 Task 時,Task.WhenAll 仍然適用,但獲取返回值該怎么做呢?

實現方式?

Task.WhenAll 可以接收混合類型的任務(Task 和 Task),但返回的是 Task 而非 Task<T[]>。因此,我們需要通過原始的有返回值任務的引用獲取結果。

using System;
using System.Threading.Tasks;class Program
{static async Task Main(string[] args){// 無返回值的任務Task task1 = Task.Run(() =>{Console.WriteLine("任務1(無返回值)開始");Task.Delay(2000).Wait();Console.WriteLine("任務1完成");});// 有返回值的任務(返回bool)Task<bool> task2 = Task.Run(() =>{Console.WriteLine("任務2(有返回值)開始");Task.Delay(1500).Wait();Console.WriteLine("任務2完成");return true; // 返回bool結果});// 同時等待兩個任務完成await Task.WhenAll(task1, task2);// 單獨獲取有返回值的任務結果bool result = await task2; // 更推薦的異步方式//bool result = task2.Result;  這么寫也是可以的!!!!!!Console.WriteLine($"任務2的返回值:{result}");Console.WriteLine("所有任務都已完成");}
}

最后給一個實際的例子

  /// <summary>/// 采集圖片進行推理/// </summary>/// <param name="index">對應的相機:0 正面相機; 1 反面相機</param>/// <returns></returns>async Task<bool> RunOne(int index){Stopwatch 計時器 = new Stopwatch();List<YOLOData> data = new List<YOLOData>();CameraConfig info;GraphicInfo graphic;//YOLODetection yolo;HObject Hobj;try{info = GlobalData.Instance.saveInfo.NeedOpenedCameraList[index];graphic = GlobalData.Instance.saveInfo.Graphics.First(t=>t.SerialNumber == info.SerialNumber);//yolo = graphic.yolo;}catch (Exception){throw new Exception($"NeedOpenedCameraList或者graphic,未找到第{index}個相機");}Task task1, task2;Task<bool> task3 = Task.FromResult(default(bool));try{計時器.Restart();//----觸發運動(從初始位置到結束位置)await motionCard.PmoveEx(axisConfigInfo, axisConfigInfo.Positions[0].Value);logger.Info($"開始運動到{axisConfigInfo.Positions[0].Value}");//觸發一次采集!CameraService.Snap(info.SerialNumber);//----觸發運動(從結束位置到初始位置)task1 = motionCard.PmoveEx(axisConfigInfo, axisConfigInfo.Positions[1].Value);logger.Info($"結束運動到{axisConfigInfo.Positions[1].Value}");//采集圖片Hobj = CameraService.GetHImage(info.SerialNumber, info.Timeout);logger.Info($"采集到圖片!");計時器.Stop();//----觸發運動(從初始位置到結束位置)task2 = motionCard.PmoveEx(axisConfigInfo, axisConfigInfo.Positions[0].Value);logger.Info($"回到開始位置!{axisConfigInfo.Positions[0].Value}");}catch (Exception ex){MessageBox.Show($"運動采集失敗{ex.Message}");return false;}var t1 = 計時器.ElapsedMilliseconds;bool b = false;if (Hobj != null){task3 = ImgCheck(graphic, Hobj);}else{MainWindowViewModel.PostGrowlEvent(info.SerialNumber + "相機獲取圖片失敗", EnumAlarmType.Warning);Task.Delay(1000).Wait();b = false;}await Task.WhenAll(task1, task2, task3);//獲取結果b = task3.Result;logger.Info($"完成一次檢測~~~~~結果為:{b}");return b;}

總結

Task.WhenAll 是 .NET 異步編程工具箱中不可或缺的一部分。它通過并發執行多個獨立的異步操作,極大地提升了應用程序的效率和響應能力。正確理解和使用 Task.WhenAll,能夠讓你的代碼更加高效、簡潔和健壯。

核心要點回顧

  • 并發啟動,然后等待全部完成
  • 顯著減少總執行時間
  • 集中處理異常聚合結果
  • 注意任務啟動時機異常處理
  • 根據場景控制并發度

掌握 Task.WhenAll,讓你的 .NET 應用在處理多任務時游刃有余!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/918098.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/918098.shtml
英文地址,請注明出處:http://en.pswp.cn/news/918098.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

微型導軌在半導體制造中有哪些高精密應用場景?

微型導軌在半導體制造中用于晶圓對準和定位系統&#xff0c;確保晶圓在光刻、蝕刻等工藝中精確移動。其高精度、高剛性、低摩擦和緊湊設計等特性&#xff0c;使其成為半導體設備實現微米級運動控制的核心部件。光刻機&#xff1a;在光刻工藝中&#xff0c;微型導軌支撐并引導掩…

全棧:Tomcat 安裝教程

Tomcat 安裝教程 安裝 Tomcat 的步驟因操作系統而異&#xff0c;以下是 Windows、Linux 和 Mac 系統的詳細安裝方法&#xff1a; 一、Windows 系統安裝 Tomcat 下載 Tomcat 訪問 Tomcat 官方網站&#xff08;http://tomcat.apache.org/&#xff09;&#xff0c;選擇適合的版本…

數據分析——Pandas庫

Pandas是Python生態系統中最強大、最流行的數據分析庫&#xff0c;專為處理結構化數據&#xff08;如表格和時間序列&#xff09;而設計。它提供了高效的數據結構和豐富的功能&#xff0c;使得數據清洗、轉換、分析和可視化變得簡單直觀。一、Pandas庫的安裝詳解1. 安裝前的準備…

數據結構-哈希表(散列表)

1.基本概念哈希表&#xff08;散列表&#xff09;&#xff1a;提高數據的查找效率哈希存儲&#xff1a;將要存儲的數據的關鍵字和存儲位置之間&#xff0c;建立起對應的關系&#xff0c; 這個關系稱之為哈希函數。存儲數據時&#xff0c;通過對應的哈希函數可以將數據映射到指定…

如何在Vue中使用拓撲圖功能

前言 該組件基于 Vue.js 和 AntV G6 構建項目特色功能 1. 豐富的節點圖標支持 本拓撲圖系統的最大特色是支持使用自定義圖片作為節點圖標 2. 智能的力導向布局 系統采用力導向布局算法&#xff0c;能夠自動優化節點位置&#xff0c;避免重疊&#xff0c;形成美觀的網絡拓撲結構…

基于dynamic的Druid 與 HikariCP 連接池集成配置區別

你提供的內容是關于 ??dynamic-datasource-spring-boot-starter?? 的詳細介紹&#xff0c;這是一個非常實用的 ??Spring Boot 多數據源動態切換組件??&#xff0c;適用于需要在單個應用中連接多個數據庫并靈活切換數據源的場景。下面我為你梳理一下該組件的核心信息與使…

算法訓練之棧

???~~~~~~歡迎光臨知星小度博客空間~~~~~~??? ???零星地變得優秀~也能拼湊出星河~??? ???我們一起努力成為更好的自己~??? ???如果這一篇博客對你有幫助~別忘了點贊分享哦~??? ???如果有什么問題可以評論區留言或者私信我哦~??? ??????個人…

OpenAI 最新開源模型 gpt-oss (Windows + Ollama/ubuntu)本地部署詳細教程

OpenAI 最近發布了其首個開源的開放權重模型gpt-oss&#xff0c;這在AI圈引起了巨大的轟動。對于廣大開發者和AI愛好者來說&#xff0c;這意味著我們終于可以在自己的機器上&#xff0c;完全本地化地運行和探索這款強大的模型了。 本教程將一步一步指導你如何在Windows系統上&…

在X86架構Linux中創建虛擬根目錄并下載指定架構(如aarch64)的軟件包(含依賴)

在X86架構Linux中創建虛擬根目錄并下載指定架構(如aarch64)的軟件包(含依賴) 在Linux系統中&#xff0c;有時候我們需要在特定的環境或架構下安裝軟件包&#xff0c;而不影響主系統。一種常見的方法是創建一個虛擬的根目錄&#xff0c;并在此環境中操作。本文將介紹如何通過創建…

scratch筆記和練習-第9課:一起來繪畫

位圖也稱為點陣圖&#xff0c;它是由許許多多的點組成的&#xff0c;這些點被稱為像素。位圖圖像可以表現豐富的多彩變化 并產生逼真的效果&#xff0c;很容易在不同軟件之間交換使用&#xff0c; 但它在保存圖像時需要記錄每一個像素的色彩信息&#xff0c;所以占用的存儲空間…

[linux] Linux:一條指令更新DDNS

Linux&#xff1a;一條指令更新DDNS 在動態IP環境下&#xff0c;如何確保我們的域名始終指向正確的公網IP地址&#xff1f;動態DNS&#xff08;DDNS&#xff09;服務為我們提供了完美的解決方案。今天&#xff0c;我將分享一個簡潔高效的Linux命令行指令&#xff0c;用于自動更…

[激光原理與應用-182]:測量儀器 - 光束型 - 光束質量分析儀

光束質量分析儀是用于精確評估激光光束特性的核心設備&#xff0c;通過測量光束的強度分布、相位分布、發散角等參數&#xff0c;為激光系統的優化、加工工藝控制及科研實驗提供關鍵數據支持。以下是光束質量分析儀的詳細解析&#xff1a;一、核心功能 - 光束強度分布分析測量內…

Linux 限制 root 登錄 IP 地址的方法

Linux 限制 root 登錄 IP 地址的方法Linux 限制 root 登錄 IP 地址的方法方法一&#xff1a;修改 SSH 配置文件方法二&#xff1a;使用 hosts.allow 和 hosts.deny 文件方法三&#xff1a;使用防火墻規則方法四&#xff1a;使用 access.conf 文件注意事項Linux 限制 root 登錄 …

Word中怎樣插入特殊符號

使用 “插入” 菜單&#xff1a;插入常用符號&#xff1a;將光標置于要插入符號的位置&#xff0c;點擊 “插入” 選項卡&#xff0c;在 “符號” 組中點擊 “符號” 按鈕&#xff0c;會彈出一個符號庫&#xff0c;里面包含了常見的標點符號、特殊字符等&#xff0c;找到所需符…

Linux 內核發包流程與路由控制實戰

Linux 內核發包流程與路由控制實戰 在網絡調優、性能優化、SDN、NFV、容器網絡等場景下&#xff0c;理解 Linux 內核發包路徑和路由控制機制是必修課。 本文將從內核網絡棧的原理入手&#xff0c;再結合 iproute2 命令和 策略路由給出實戰案例。一、Linux 內核發包流程&#xf…

點播服務器

早期的時候&#xff0c;用 live555 作為 rtsp 點播服務器&#xff1b;現在比較常用的 流媒體服務器比較多&#xff1b;這里比較簡單的&#xff0c;可以用 ZLMediakit&#xff1b;可以支持 ffmeg 退流 到ZLMediakit&#xff0c;然后別的客戶端從 ZLMediakit 服務器拉流&#xff…

分享超圖提供的、很不錯的WebGIS學習資源

最近在學習了解Supermap iclient&#xff0c;發現官方提供的幫助文檔、GIS學堂真的不錯&#xff0c;解釋了很多的內容。 官方modern-web-gis-in-action文檔的網址如下&#xff1a;https://iclient.supermap.io/web/books/modern-web-gis-in-action/&#xff0c;在其中介紹了現代…

通信算法之298: verilog語法generate和for介紹

在 Verilog 中&#xff0c;generate和for是實現參數化設計和模塊實例化復用的重要工具&#xff0c;尤其在需要根據參數動態生成邏輯時非常有用。以下是它們的使用方法和區別&#xff1a;1. for循環&#xff08;過程塊內&#xff09;for循環主要用于過程塊&#xff08;always/in…

laravel在cli模式下輸出格式漂亮一些

在 Laravel 的 CLI 模式下&#xff0c;可以通過以下方式讓命令行輸出更加美觀和專業&#xff1a; 1. 使用 Artisan 輸出助手方法 Laravel 提供了多種輸出樣式方法&#xff1a; public function handle() {// 基礎樣式$this->info(成功信息 - 綠色); // 綠色$this->err…

大數據管理與應用學什么?就業前景怎么樣?

前言在數字經濟蓬勃發展的今天&#xff0c;大數據已經成為推動社會進步的核心生產要素。大數據管理與應用作為新興交叉學科&#xff0c;正受到越來越多學生和企業的關注。本文將全面剖析該專業的課程體系、核心技能要求&#xff0c;詳細介紹CDA數據分析師認證的備考策略&#x…