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

  • 351
  • 0

最近在看一些 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.WhenAllParallel.ForEach
適用場景I/O(等待遠端)CPU heavy(做計算)
ThreadPool 使用幾乎不佔用佔滿 CPU 核心
效能同時等待 → 快同時計算 → 快
不適合CPU heavyI/O
呼叫 async可以不行(會 deadlock 或變慢)

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

你的 JSON 檔案來源用什麼?原因
SSD / NVMe 本地小檔大量讀取Parallel.ForEachDeserialize 超吃 CPU
本機 HDD 大量小檔Parallel.ForEach仍然 CPU bottleneck
Network Share / NAS(慢)Task.WhenAllI/O latency 很高
AWS S3 / GCS 下載Task.WhenAll典型 I/O Bound
單檔很大(>100MB)Task.WhenAllI/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,當然這都是你最後再優化上面再拉升效能的手段之一

 

--

本文原文首發於我的個人部落格:平行處理到底用哪個?Task.WhenAll , Parallel.ForEach 最簡單選法筆記

---

---

Yesterday I wrote down the code. I bet I could be your hero. I am a mighty little programmer.