C#中Task.WhenAll和Task.WhenAny的使用与区别小结

 更新时间:2025年12月28日 10:28:13   作者:无风听海  
Task.WhenAll和Task.WhenAny是用于组合多个Task完成信号的工具,本文主要介绍了C#中Task.WhenAll和Task.WhenAny的使用与区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、先给终极结论

Task.WhenAll 和 Task.WhenAny 都不是执行器,而是“完成信号的组合器”。
它们:

  • ❌ 不创建线程
  • ❌ 不调度任务
  • ❌ 不产生并发
  • ✅ 只监听 Task 的完成

并发发生在 Task 被“创建/启动”的那一刻,而不是 WhenAll/WhenAny。

二、两者的本质模型(抽象层)

1、 Task.WhenAll —— “全部完成门闩(AND Gate)”

T1 ─┐
T2 ─┼─▶ [ All Completed ] ─▶ Completed Task
T3 ─┘

语义:

  • 所有 Task 完成 → WhenAll 完成
  • 任一 Task 失败 → WhenAll 失败(但仍等所有结束)

2、 Task.WhenAny —— “第一个完成门闩(OR Gate)”

T1 ─┐
T2 ─┼─▶ [ First Completed ] ─▶ Completed Task<Task>
T3 ─┘

语义:

  • 第一个完成者胜出
  • 其他 Task 不受影响,继续运行

三、底层实现原理(核心机制)

1、WhenAll 的内部机制(简化版)

  • 对每个 Task 注册 continuation

  • 使用一个 原子计数器(remaining)

  • 每完成一个 Task:

    • 记录状态(成功 / 失败 / 取消)
    • Interlocked.Decrement(remaining)
  • remaining == 0

    • 设置 WhenAll Task 的最终状态

📌 没有线程等待,完全事件驱动

2、WhenAny 的内部机制(简化版)

对每个 Task 注册 continuation

第一个完成的 Task:

  • 调用 TrySetResult(task)

后续完成者:

  • 直接忽略

📌 只有一个能“赢”,没有计数器

3、为什么 WhenAny 返回Task<Task>?

  • 外层 Task:表示“谁先完成”
  • 内层 Task:表示“完成的那个任务本身”

WhenAny 返回的是“胜者句柄”,不是结果

四、执行与调度层面的关键差异

维度WhenAllWhenAny
等待策略全部第一个
返回类型Task / Task<T[]>Task
是否阻塞线程
continuation 数量NN
内部同步原子计数CAS / TrySet
并发控制

二者都只做“信号组合”,不做“任务调度”

五、异常语义(非常重要)

1、 WhenAll 的异常规则

  • 所有 Task 都会执行到结束

  • 如果有异常:

    • 内部保存 AggregateException

    • await WhenAll 时:

      • 抛出 第一个异常
      • 其他异常仍可从各 Task 中取
try
{
    await Task.WhenAll(tasks);
}
catch
{
    var all = tasks
        .Where(t => t.IsFaulted)
        .SelectMany(t => t.Exception!.InnerExceptions);
}

2、 WhenAny 的异常规则

  • 如果“第一个完成的 Task”失败:

    • await completedTask 会直接抛异常
  • 其他 Task 的异常:

    • 不会被观察
    • 必须手动处理,否则可能变成未观察异常

📌 WhenAny + 异常 = 高风险组合

六、取消语义(常被误解)

1、WhenAll

  • 如果任一 Task 被取消:

    • WhenAll 最终可能是 Canceled
  • 但:

    • 不会主动取消其他 Task

2、 WhenAny

  • 不会取消任何 Task
  • 取消必须由你显式触发
var winner = await Task.WhenAny(tasks);
cts.Cancel(); // 你自己的责任

七、性能特征(底层视角)

1、 WhenAll / WhenAny 本身的成本

  • 少量对象分配
  • N 个 continuation
  • 原子操作 / CAS

👉 几乎可以忽略

2、真正的性能瓶颈来自:

  • Task 创建方式
  • IO / CPU 本身
  • Task.Run / ThreadPool 使用
  • 并发规模失控

八、典型使用范式(工程级)

1、 WhenAll —— 并发聚合(最常用)

var t1 = GetUserAsync();
var t2 = GetOrdersAsync();

await Task.WhenAll(t1, t2);

return new
{
    User = await t1,
    Orders = await t2
};

适用场景

  • 多个独立 IO
  • 聚合响应
  • Web API

2、 WhenAny —— 竞速 / 超时 / 降级

超时模式

var work = DoWorkAsync();
var timeout = Task.Delay(2000);

if (await Task.WhenAny(work, timeout) == timeout)
    throw new TimeoutException();

await work;

主备切换

var tasks = new[]
{
    CallPrimaryAsync(),
    CallSecondaryAsync()
};

var winner = await Task.WhenAny(tasks);
return await winner;

⚠️ 记得取消失败者

九、常见错误总结

❌ 把 WhenAll 当“并行器”
❌ 在循环里直接 await(伪并发)
❌ WhenAny 后忽略未完成 Task
❌ WhenAll + Task.Run(Web)
❌ 忽略异常和取消

十、设计层面的黄金准则

并发 = Task 创建时机
组合 = WhenAll / WhenAny
调度 = ThreadPool / Parallel

WhenAll / WhenAny 决定“怎么等”,
而不是“怎么跑”。

十一、一句话终极总结

Task.WhenAll 是一个原子计数器驱动的完成门闩
Task.WhenAny 是一个 CAS 驱动的竞速门闩

它们本身几乎“没有重量”,
但决定了整个 async 架构的形状。

到此这篇关于C#中Task.WhenAll和Task.WhenAny的使用与区别小结的文章就介绍到这了,更多相关C# Task.WhenAll和Task.WhenAny内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • asp.net关于Cookie跨域(域名)的问题

    asp.net关于Cookie跨域(域名)的问题

    Cookie是一个伟大的发明,它允许Web开发者保留他们的用户的登录状态。但是当你的站点有一个以上的域名时就会出现问题了。在Cookie规范上说,一个cookie只能用于一个域名,不能够发给其它的域名。因此,如果在浏览器中对一个域名设置了一个cookie,这个cookie对于其它的域名将无效。如果你想让你的用户从你的站点中的其中一个进行登录,同时也可以在其它域名上进行登录,这可真是一个大难题。
    2012-12-12
  • 浅析.net core 抛异常对性能影响

    浅析.net core 抛异常对性能影响

    在.net项目中使用自定义异常来处理业务很爽,但是又担心大量抛业务异常存在性能问题,下面通过本文介绍.net core 抛异常对性能影响的求证之路,需要的朋友可以参考下
    2022-06-06
  • asp .net core静态文件资源的深入讲解

    asp .net core静态文件资源的深入讲解

    这篇文章主要给大家介绍了关于asp .net core静态文件资源的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • ASP.NET MVC使用typeahead.js实现输入智能提示功能

    ASP.NET MVC使用typeahead.js实现输入智能提示功能

    这篇文章介绍了ASP.NET MVC使用typeahead.js实现输入智能提示功能的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-09-09
  • .NET Core Web APi大文件分片上传研究实现

    .NET Core Web APi大文件分片上传研究实现

    这篇文章主要介绍了.NET Core Web APi大文件分片上传研究实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • ASP.NET MVC 开发微信支付H5的实现示例(外置浏览器支付)

    ASP.NET MVC 开发微信支付H5的实现示例(外置浏览器支付)

    这篇文章主要介绍了ASP.NET MVC 开发微信支付H5的实现示例(外置浏览器支付),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • .net中捕捉全局未处理异常的三种方式示例

    .net中捕捉全局未处理异常的三种方式示例

    这篇文章主要给大家介绍了关于.net中捕捉全局未处理异常的三种方式,分别是Page_Error处理页面级未处理异常、通过HttpModule来捕获未处理的异常以及通过Global中捕获未处理的异常,需要的朋友可以参考下
    2018-06-06
  • asp.net 退出登陆(解决退出后点击浏览器后退问题仍然可回到页面问题)

    asp.net 退出登陆(解决退出后点击浏览器后退问题仍然可回到页面问题)

    退出登陆是再常见不过的了,先清除Session,再转到登陆页面
    2009-04-04
  • 浅析DataBinder.Eval和Eval的区别

    浅析DataBinder.Eval和Eval的区别

    缩短的Eval语法与DataBinder.Eval的不同点在于,Eval会根据最近的容器对象(例如DataListItem)的DataItem属性来自动地解析字段,而DataBinder.Eval需要使用参数来指定容器
    2013-08-08
  • .Net读取Excel 返回DataTable实例代码

    .Net读取Excel 返回DataTable实例代码

    这篇文章主要介绍了.Net读取Excel 返回DataTable实例代码,有需要的朋友可以参考一下
    2014-01-01

最新评论