[C#] 平行處理到底用哪個?Task.WhenAll , Parallel.ForEach 最簡單選法筆記

2025-12-10

最近在看一些 open source 的 專案,看到一個關鍵字 Task.WhenAll ,看了一下跟 Parallel.ForEach

看起來不是差不多的東西嗎? 問一下 GPT 原來是有差異的,今天筆記一下,希望自己以後可以用的比較恰當..


先一句話簡單兩個區別:

Task.WhenAll = 非同步 I/O Turbo 模式

Parallel.ForEach = CPU 多核心 Turbo 模式

所以簡單的說,如果大量平行呼叫服務操作建議使用 Task.WhenAll , 這點是我很常犯的,畢竟我常常就是無腦 Parallel.ForEach 

直接做平行處理

這舉幾個常用案例 - 

1. 大量 I/O ,這邊裡有一個迷思,如果我是大量讀取檔案做反序列化,算是大量 File I/O 處理嗎?

雖然是大量 File I/O 讀取,但是因為你會處理檔案譬如反序列化,或是讀取出來做一些位元推移,所以建議還是使用 Parallel.ForEach

畢竟這還是處於大量CPU 處理範圍,但是如果是呼叫呼叫外部 API則使用 Task.WhenAll

var urls = new[] { "https://example.com/a", "https://example.com/b", "https://example.com/c" }; var http = new HttpClient(); // 真正適合 I/O 的寫法:全部同時等待,不吃 CPU var tasks = urls.Select(async url => { Console.WriteLine($"Start {url}"); var res = await http.GetStringAsync(url); Console.WriteLine($"Done {url}"); return res; }); var results = await Task.WhenAll(tasks);


2. 大量 CPU ,比較常見就是讀取大量檔案(而且要處理檔案)或是資料庫資料處理。

var files = Directory.GetFiles("./images"); var files = Directory.GetFiles("./images"); Parallel.ForEach(files, file => { using var img = Image.Load(file); img.Mutate(x => x.Resize(200, 200)); img.Save($"thumb/{Path.GetFileName(file)}"); });


3. 無法判斷怎麼辦?! 其實我個人都會先用 Parallel.ForEach ,但是只要想到這 Task 可能不需要使用到大量 CPU,只是數量多而已

就可以使用 Task.WhenAll ,GPT 是推薦,兩個可以分批使用先透過 Task.WhenAll 拉回資料後,使用 Parallel.ForEach 

這邊給上  GPT 給我的 快速查表

-


功能 Task.WhenAll Parallel.ForEach
適用場景 I/O(等待遠端) CPU heavy(做計算)
ThreadPool 使用 幾乎不佔用 佔滿 CPU 核心
效能 同時等待 → 快 同時計算 → 快
不適合 CPU heavy I/O
呼叫 async 可以 不行(會 deadlock 或變慢)


這邊也附上一些狀況推薦使用的情形 GPT 提供的推演狀況

你的 JSON 檔案來源 用什麼? 原因
SSD / NVMe 本地小檔大量讀取 Parallel.ForEach Deserialize 超吃 CPU
本機 HDD 大量小檔 Parallel.ForEach 仍然 CPU bottleneck
Network Share / NAS(慢) Task.WhenAll I/O latency 很高
AWS S3 / GCS 下載 Task.WhenAll 典型 I/O Bound
單檔很大(>100MB) Task.WhenAll I/O Reading 才是瓶頸
讀完 JSON 後還要大量 CPU 處理 Parallel.ForEach 全程 CPU 為主


先筆記到這邊,總結一下,Task.WhenAll 和 Parallel.ForEach 根本不是替代品

Task.WhenAll 適合等東西:I/O bound、外部 API、資料庫、檔案讀寫

Parallel.ForEach 適合算東西:CPU bound、影像處理、反序列化、資料批次運算

總之等的用 Task.WhenAll,算的用 Parallel.ForEach,當然這都是你最後再優化上面再拉升效能的手段之一



當麻許的碎念筆記 2014 | Donma Hsu Design.