C# 遍歷方法全對比:`Parallel.ForEach`、`List.ForEach`、`foreach` 到底怎么選?
遍歷集合是 C# 程序員天天要干的事。數(shù)據(jù)多了、邏輯復雜了,性能、異步、并發(fā)就統(tǒng)統(tǒng)成了問題。C# 提供了幾種不同的遍歷方式,各有優(yōu)缺點,今天我們來用真實代碼和具體場景,一次講清楚:
- Parallel.ForEach 和 Parallel.ForEachAsync
- List<T>.ForEach
- foreach(包括配合異步方法)
1. Parallel.ForEach:多線程并發(fā)執(zhí)行,性能猛獸
當你有大量數(shù)據(jù)需要同時處理,而且每個處理之間沒有依賴關(guān)系,用 Parallel.ForEach 能顯著提升性能。
Parallel.ForEach(myList, item =>
{
ProcessHeavy(item); // 耗時的同步任務
});
這個方法會自動幫你分配線程池中的線程去并發(fā)執(zhí)行任務。唯一要注意的是,它不保證順序,多個任務是同時跑的。如果你訪問了共享資源(比如同一個文件、變量),就要手動加鎖或用線程安全的方式處理。
.NET 6 起還支持異步版本:
await Parallel.ForEachAsync(myList, async (item, token) =>
{
await ProcessAsync(item); // 異步耗時任務,如 HTTP 請求
});
這個非常適合需要同時跑多個異步請求,比如發(fā)起 100 個 API 調(diào)用、同時上傳一堆文件等。
適合場景:
- 并發(fā)執(zhí)行沒有順序依賴的任務
- 大批量數(shù)據(jù)處理
- 高性能需求場景,如后臺服務、圖像處理等
2. List<T>.ForEach:優(yōu)雅簡潔,但局限也多
很多人說的 “Enumerable.ForEach” 其實并不存在,真正的是 List<T>.ForEach 方法。它是 List 自帶的實例方法,不是 LINQ 擴展。
var list = new List<int> { 1, 2, 3 };
list.ForEach(item => Console.WriteLine(item));
看起來非常簡潔,適合快速寫小腳本或者 UI 層的簡單邏輯處理。但它只支持 List 類型,而且不能用于異步操作。你要是這樣寫:
list.ForEach(async item => await DoSomethingAsync(item)); // 錯誤寫法!
這段代碼會變成 async void,出了錯都捕不到,調(diào)試困難,不建議這樣使用。
適合場景:
- 小數(shù)據(jù)量操作
- 不涉及異步或并發(fā)的邏輯
- 代碼潔癖患者追求簡短寫法
3. foreach + async:穩(wěn)妥靠譜,順序清晰
最經(jīng)典的寫法仍然是 foreach,它的好處是穩(wěn)。你可以明確知道順序、執(zhí)行時機、異常處理,配合異步也很好用。
foreach (var item in myList)
{
await DoSomethingAsync(item); // 一個個執(zhí)行
}
雖然不能并發(fā),但非常適合對順序敏感的場景,比如依次寫數(shù)據(jù)庫、依次上傳文件、依次記錄日志等。
.NET 還支持 await foreach 遍歷異步流,比如從數(shù)據(jù)庫流式讀取數(shù)據(jù):
await foreach (var row in GetDataAsync())
{
Console.WriteLine(row);
}
這類寫法適合消息隊列、數(shù)據(jù)庫分頁加載、SignalR 等場景。
適合場景:
- 需要確保執(zhí)行順序
- 異步操作逐個進行,穩(wěn)定性優(yōu)先
- 可配合異步流讀取大數(shù)據(jù)量
4. 對比總結(jié)表
遍歷方式 | 是否支持并發(fā) | 是否支持異步 | 順序是否保證 | 支持的集合類型 | 推薦使用場景 |
| ? | ? | ? | 所有 | 并行處理 CPU 密集型任務 |
| ? | ? | ? | 所有 | 并發(fā)處理異步任務(如接口、I/O) |
| ? | ?(?不支持) | ? | 僅限 | 小量數(shù)據(jù)處理,語法簡潔 |
| ? | ? | ? | 所有 | 順序異步執(zhí)行,控制清晰 |
(異步流) | ? | ? | ? | 異步可枚舉對象 | 異步流處理,如數(shù)據(jù)庫流、消息流等 |
結(jié)語
- 如果你任務之間沒啥依賴,又想快, 并發(fā)用 Parallel.ForEach 或 Parallel.ForEachAsync 。
- 如果只是小腳本、小功能,List<T>.ForEach 最舒服,但別寫異步邏輯進去。
- 如果你想代碼靠譜、不出事,特別是對順序敏感的異步操作,還是老老實實用 foreach + await。