You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Parallel ForEach未等待HttpClient完成,finally提前执行问题咨询

问题分析:Parallel.ForEach结合异步HttpClient时finally提前执行的原因及解决办法

咱们先还原一下你的问题场景:你用Parallel.ForEach调用包含HttpClient.GetAsync的异步方法Do1时,finally块里的"Done"会在HTTP请求完成前就打印出来;但换成调用Do2就正常,"Done"会在所有输出之后才出现。而且你明确希望继续用Parallel.ForEach而不是Task.WhenAll。

为什么HttpClient的场景会不符合预期?

核心原因是**Parallel.ForEach是为同步代码设计的,它完全不理解异步任务**。

当你写Parallel.ForEach(numbers, async (number) => { await Do1(); })的时候,这个异步lambda其实是async void类型(因为Parallel.ForEach接受的是Action<T>委托,异步版本会隐式转为async void)。而async void方法有个关键特性:它在遇到第一个await时就会立即返回,不会等待后续异步操作完成。

所以流程变成了这样:

  1. Parallel.ForEach启动每个lambda,遇到await Do1()就直接认为这个委托执行完毕了
  2. 很快所有lambda都"执行完",外层的Task.Run任务也随之结束
  3. Main方法里的await Task.Run(...)完成,直接进入finally块打印"Done"
  4. 此时Do1里的HTTP请求还在后台异步执行,等请求完成后才会打印StatusCode

而调用Do2时正常的原因是:Do2是async Task方法,当你在Main里await Do2()时,Main会等待Do2的整个执行流程完成——包括循环里的每一个await Multiply(i),所以所有输出都完成后才会进入finally块。

用Parallel.ForEach实现预期效果的解决方案

既然你想保留Parallel.ForEach,可以用以下两种方式让它等待所有异步任务完成:

方案1:同步等待异步任务(简单直接,但会阻塞线程)

把异步lambda改成同步委托,在里面用.GetAwaiter().GetResult()等待Do1完成,这样Parallel.ForEach会确保每个任务执行完再处理下一个:

await Task.Run(() => { 
    Parallel.ForEach(numbers, (number) => { 
        Do1().GetAwaiter().GetResult(); 
    }); 
});

方案2:收集所有异步任务后统一等待(保留异步优势)

用线程安全的集合收集每个Do1返回的Task,在Parallel.ForEach启动完所有任务后,统一等待全部完成:

await Task.Run(() => { 
    var tasks = new ConcurrentBag<Task>();
    Parallel.ForEach(numbers, (number) => { 
        tasks.Add(Do1()); 
    });
    Task.WaitAll(tasks.ToArray());
});

这个方案里Parallel.ForEach负责高效启动所有异步请求,之后Task.WaitAll确保所有请求完成后,外层的Task.Run才会结束,这样finally块就会在所有StatusCode输出后再打印"Done"。

内容的提问来源于stack exchange,提问作者Shak Ham

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:10:07