遍历集合是 C# 程序员天天要干的事。数据多了、逻辑复杂了,性能、异步、并发就统统成了问题。C# 提供了几种不同的遍历方式,各有优缺点,今天我们来用真实代码和具体场景,一次讲清楚:
Parallel.ForEach
和 Parallel.ForEachAsync
List<T>.ForEach
foreach
(包括配合异步方法)Parallel.ForEach
:多线程并发执行,性能猛兽当你有大量数据需要同时处理,而且每个处理之间没有依赖关系,用 Parallel.ForEach
能显著提升性能。
Parallel.ForEach(myList, item =>
{
ProcessHeavy(item); // 耗时的同步任务
});
这个方法会自动帮你分配线程池中的线程去并发执行任务。唯一要注意的是,它不保证顺序,多个任务是同时跑的。如果你访问了共享资源(比如同一个文件、变量),就要手动加锁或用线程安全的方式处理。
.NET 6 起还支持异步版本:
await Parallel.ForEachAsync(myList, async (item, token) =>
{
await ProcessAsync(item); // 异步耗时任务,如 HTTP 请求
});
这个非常适合需要同时跑多个异步请求,比如发起 100 个 API 调用、同时上传一堆文件等。
适合场景:
List<T>.ForEach
:优雅简洁,但局限也多很多人说的 “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
,出了错都捕不到,调试困难,不建议这样使用。
适合场景:
foreach
+ async:稳妥靠谱,顺序清晰最经典的写法仍然是 foreach
,它的好处是稳。你可以明确知道顺序、执行时机、异常处理,配合异步也很好用。
foreach (var item in myList)
{
await DoSomethingAsync(item); // 一个个执行
}
虽然不能并发,但非常适合对顺序敏感的场景,比如依次写数据库、依次上传文件、依次记录日志等。
.NET 还支持 await foreach
遍历异步流,比如从数据库流式读取数据:
await foreach (var row in GetDataAsync())
{
Console.WriteLine(row);
}
这类写法适合消息队列、数据库分页加载、SignalR 等场景。
适合场景:
遍历方式 | 是否支持并发 | 是否支持异步 | 顺序是否保证 | 支持的集合类型 | 推荐使用场景 |
---|---|---|---|---|---|
Parallel.ForEach | ✅ | ❌ | ❌ | 所有 IEnumerable | 并行处理 CPU 密集型任务 |
Parallel.ForEachAsync | ✅ | ✅ | ❌ | 所有 IEnumerable | 并发处理异步任务(如接口、I/O) |
List<T>.ForEach | ❌ | ❌(⚠不支持) | ✅ | 仅限 List<T> | 小量数据处理,语法简洁 |
foreach + await | ❌ | ✅ | ✅ | 所有 IEnumerable | 顺序异步执行,控制清晰 |
await foreach(异步流) | ❌ | ✅ | ✅ | 异步可枚举对象 | 异步流处理,如数据库流、消息流等 |
Parallel.ForEach
或 Parallel.ForEachAsync
。List<T>.ForEach
最舒服,但别写异步逻辑进去。foreach + await
。如果你觉得这篇文章对你有帮助,不妨点赞、收藏或转发!