用途
该技能用于在 .NET 中处理并发操作时,帮助开发者判断应使用哪种并发抽象。适用场景包括:决定如何处理 .NET 中的并发操作;评估是否使用 async/await、Channels、Akka.NET 或其他抽象;在考虑使用锁、信号量等同步原语时进行权衡;需要处理带背压、批处理或去抖动的数据流;跨多个并发实体管理状态。
核心理念
- 从简单开始,只在确有需要时升级。大多数并发问题可用 async/await 解决,只有 async/await 无法干净处理时才使用更复杂的工具。
- 尽量避免共享可变状态。最好的并发处理方式是通过设计消除它:不可变数据、消息传递、隔离状态(如 actor)可消除整类 bug。
- 锁应是例外而非常规。无法避免共享可变状态时,优先级为:重新设计以规避(不可变性、消息传递、actor 隔离)→ 使用 System.Collections.Concurrent(如 ConcurrentDictionary)→ 使用 Channel<T> 通过消息传递串行化访问 → 最后才用 lock 处理简单、短小的临界区。
决策树
- 等待 I/O(HTTP、数据库、文件)→ 使用 async/await
- 并行处理集合(CPU 密集型)→ 使用 Parallel.ForEachAsync
- 生产者/消费者模式(工作队列)→ 使用 System.Threading.Channels
- UI 事件处理(去抖动、节流、组合)→ 使用 Reactive Extensions (Rx)
- 服务端流处理(背压、批处理)→ 使用 Akka.NET Streams
- 具有复杂转换的状态机 → 使用 Akka.NET Actors(Become 模式)
- 管理大量独立实体的状态 → 使用 Akka.NET Actors(entity-per-actor)
- 协调多个异步操作 → 使用 Task.WhenAll / Task.WhenAny
- 以上都不适用 → 自问是否真的需要共享可变状态;是则考虑重新设计,确实无法避免则用 Channels 或 Actors 串行化访问。
分级方案
Level 1:async/await(默认选择) 用于 I/O 密集型操作、非阻塞等待及大多数日常并发。关键原则:始终接受 CancellationToken;在库代码中使用 ConfigureAwait(false);不要阻塞异步代码。示例包括简单异步 I/O 以及用 Task.WhenAll 并行执行独立异步操作。
Level 2:Parallel.ForEachAsync(CPU 密集型并行) 用于并行处理集合,且工作为 CPU 密集型或需要受控并发。可通过 ParallelOptions 设置 MaxDegreeOfParallelism 和 CancellationToken。不适用于纯 I/O 操作、需要保持顺序或需要背压的场景。
Level 3:System.Threading.Channels(生产者/消费者) 用于工作队列、生产者/消费者模式、解耦生产者与消费者。示例中通过 Channel.CreateBounded 创建有界通道,设置 FullMode 为 Wait,生产者用 WriteAsync 写入,消费者用 ReadAllAsync 读取,最后调用 Complete。Channels 适合解耦速度、带背压的缓冲、向工作线程扇出、后台队列;不适合复杂流操作(批处理、窗口化)、有状态的逐实体处理、复杂监督。
Level 4+:Akka.NET Streams、Reactive Extensions、Actors 用于需要流处理、UI 事件组合或有状态实体管理的高级场景。Akka.NET Streams 擅长服务端批处理、节流和背压;Reactive Extensions 适合 UI 事件组合;Akka.NET Actors 处理 entity-per-actor 模式、使用 Become() 的状态机以及通过 Cluster Sharding 的分布式系统。
反模式
- 用锁处理业务逻辑:应改用 actor 或 Channel 串行化访问。
- 手动线程管理:应使用 Task.Run 或更好的抽象。
- 在异步代码中阻塞:如 .Result 有死锁风险,应全程异步。
- 无保护的共享可变状态:多任务修改共享状态会产生竞态,应使用 ConcurrentBag 等并发集合。
快速参考
- 等待 I/O → async/await(HTTP 调用、数据库查询)
- 并行 CPU 工作 → Parallel.ForEachAsync(图像处理、计算)
- 工作队列 → Channel<T>(后台作业处理)
- 带去抖动/节流的 UI 事件 → Reactive Extensions(输入即搜索、自动保存)
- 服务端批处理/节流 → Akka.NET Streams(事件聚合、限流)
- 状态机 → Akka.NET Actors(支付流程、订单生命周期)
- 实体状态管理 → Akka.NET Actors(订单管理、用户会话)
- 触发多个异步操作 → Task.WhenAll(加载仪表盘数据)
- 竞速多个异步操作 → Task.WhenAny(超时与回退)
- 周期性工作 → PeriodicTimer(健康检查、轮询)
升级路径
从 async/await 开始,需要并行时升级到 Parallel.ForEachAsync,需要生产者/消费者时升级到 Channel<T>,需要 UI 事件组合时升级到 Reactive Extensions,需要服务端流处理时升级到 Akka.NET Streams,需要状态机或实体管理时升级到 Akka.NET Actors。只在有具体需求时升级,不要“以防万一”就使用 actors 或 streams。
参考文件
advanced-concurrency.md 涵盖 Akka.NET Streams、Reactive Extensions、Akka.NET Actors(entity-per-actor、状态机、集群分片)以及异步本地函数模式。