ForEachAsync 不是 .NET 原生 API,不存在于 System.Collections.Generic 或 System.Linq 中,而是开发者自定义或第三方库提供的异步遍历方法,基于 Task.WhenAll 实现并发执行,需注意限流与异常聚合;Parallel.ForEach 是 .NET 内置同步并行工具,不支持直接 await 异步操作。

ForEachAsync 不是 .NET 原生 API
直接说结论:ForEachAsync 不存在于 System.Collections.Generic 或 System.Linq 中。它常被误认为是 List 的扩展方法,实际是开发者自己写的异步遍历辅助方法,或来自第三方库(如 Microsoft.VisualStudio.Threading 或社区 NuGet 包)。Parallel.ForEach 则是 .NET Framework 4+ 内置的并行同步执行工具,位于 System.Threading.Tasks 命名空间。
ForEachAsync 通常怎么实现和使用
常见自定义 ForEachAsync 是基于 Task.WhenAll 的并发控制,不是串行 await 每一项(那叫 foreach + await),而是批量触发所有异步操作再统一等待:
public static async Task ForEachAsync(this IEnumerable source, Func body) { if (source == null) throw new ArgumentNullException(nameof(source)); if (body == null) throw new ArgumentNullException(nameof(body)); var tasks = source.Select(item => body(item)); await Task.WhenAll(tasks); }
使用时注意:
-
ForEachAsync默认不控制并发数,1000 个元素就并发 1000 个Task,可能压垮服务或耗尽连接池 - 若需限流,得改用
SemaphoreSlim包裹body,或借助System.Threading.Tasks.Dataflow的ActionBlock - 异常行为:任意一个
Task抛出异常,Task.WhenAll就会以AggregateException形式抛出,所有异常都会被捕获(不像串行foreach + await遇到第一个异常就停)
Parallel.ForEach 是同步阻塞式并行,不能直接 await 异步操作
Parallel.ForEach 在每个线程上执行的是同步委托 Action,传入 async lambda 会导致“火把式异步”(fire-and-forget)——编译能过,但实际只启动了 Task 并立即返回,Parallel 不等它完成就继续下一项,最终结果不可控:
Parallel.ForEach(items, item =>
{
SomeAsyncOperation(item).Wait(); // ❌ 不推荐:阻塞线程,易死锁、拖慢吞吐
});
正确做法只有两个:
2013年07月06日 V1.60 升级包更新方式:admin文件夹改成你后台目录名,然后补丁包里的所有文件覆盖进去。1.[新增]后台引导页加入非IE浏览器提示,后台部分功能在非IE浏览器下可能没法使用2.[改进]淘客商品管理 首页 列表页 内容页 的下拉项加入颜色来区别不同项3.[改进]后台新增/修改淘客商品,增加淘宝字样的图标和天猫字样图标改成天猫logo图标4.[改进]为统一名称,“分类”改
- 坚持同步逻辑:所有操作必须是 CPU-bound 或已同步封装(如
File.ReadAllBytes) - 改用异步方案:放弃
Parallel.ForEach,回到ForEachAsync(自定义或第三方)或Task.WhenAll+Select
性能差异明显:Parallel.ForEach 适合密集计算;ForEachAsync 适合 I/O 密集(HTTP 请求、DB 查询),但必须小心资源竞争与并发上限。
别混淆 Task.Run + ForEachAsync 和 Parallel.ForEach
有人试图用 Task.Run(() => Parallel.ForEach(...)) 把同步并行“包一层”变成异步,这是典型误解:
- 没解决根本问题:内部仍是同步执行,只是挪到了后台线程池线程上
- 额外增加调度开销,且无法取消、难以监控进度
- 如果
Parallel.ForEach里混了await,一样会掉进“未等待异步任务”的陷阱
真正需要异步并行时,优先选明确支持 Func 的抽象(如 AsyncEnumerable 的 ForEachAwaitAsync,.NET 6+ 的 IAsyncEnumerable 扩展),或者自己加信号量限流的 ForEachAsync 实现。
最易被忽略的一点:异步并发数 ≠ 线程数,而 Parallel.ForEach 的度量单位是线程。IO 操作不占线程,但可能占 socket、数据库连接、API 配额——这些才是 ForEachAsync 真正要管的资源。









