为什么在 for 循环里直接 await 会变慢
因为
await默认是顺序等待:前一个异步操作没完成,后一个根本不会发起。比如调用 10 次
HttpClient.GetAsync(),实际是串行发请求,总耗时 ≈ 所有请求耗时之和。
这不是 await 本身的问题,而是写法让它“不敢并发”。关键在控制权交还时机和任务调度逻辑。
如何让多个 async 调用真正并发执行
把异步操作包装成
Task,先全部启动,再统一
await Task.WhenAll(...)等待全部完成。 ✅ 正确做法:用
var tasks = urls.Select(url => client.GetAsync(url)).ToArray(); await Task.WhenAll(tasks);</li>
<li>❌ 错误写法:<code>for (int i = 0; i < urls.Length; i++) { await client.GetAsync(urls[i]); } —— 完全串行
⚠️ 注意:Task.WhenAll不会改变异常行为 —— 任一任务失败,整个
await就抛出
AggregateException,需用
try/catch或检查
task.Exception⚠️ 内存与连接数:并发太多可能触发
HttpClient连接池限制或服务器限流,建议配合
SemaphoreSlim限流
需要按顺序处理结果时怎么写
Task.WhenAll返回的
Task<t></t>结果数组,下标和原始输入顺序严格一致。不需要额外排序或映射。
var tasks = ids.Select(async id => {
var res = await client.GetAsync($"/api/item/{id}");
return await res.Content.ReadFromJsonAsync<Item>();
});
var results = await Task.WhenAll(tasks); // results[0] 对应 ids[0] 的结果
如果中间某次请求失败,对应位置的
results[i]会是
null(除非你显式 throw),但更稳妥的是用
Task.WhenAll+ 单独 try/catch 包裹每个 lambda。
foreach 里用 async lambda 为什么编译不过
因为
async void或
async Func<t></t>在
foreach中容易捕获错误的变量(闭包陷阱),且编译器不支持直接在
foreach语句块中写
await(会报 CS1992 “无法在匿名方法、lambda 表达式或查询表达式中使用 await”)。 ✅ 解决:改用
Select+
asynclambda,或提前把循环变量复制到局部变量(如
var current = item;) ✅ 更安全写法:
foreach (var item in list) { var t = ProcessAsync(item); tasks.Add(t); },然后 await Task.WhenAll(tasks)⚠️ 切勿写:
foreach (var item in list) { await ProcessAsync(item); } —— 又回到串行
真正难的不是语法,是判断哪些操作必须等、哪些可以放一起跑,以及失败后要不要重试、要不要记录中间状态。这些没法靠 await自动解决。
