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