当处理逻辑仅为暂存转发时应选BufferBlock,因其无委托开销、纯内存队列、吞吐更高;TransformBlock需有实际转换或异步操作才具价值。

什么时候该用 BufferBlock<t></t> 而不是 TransformBlock<tin tout></tin>
当你的处理逻辑只是「暂存 + 转发」,不涉及计算、转换或异步等待时,BufferBlock<t></t> 是更轻量的选择。它没有执行委托,也不触发调度,纯内存队列,吞吐更高。而 TransformBlock<tin tout></tin> 一旦设置了 Func<tin tout></tin> 或 Func<tin task>></tin>,就会在每次接收数据时调用委托 —— 即使你只写 x => x,也多了一层委托开销和上下文切换。
常见误用场景:用 TransformBlock 做单纯缓冲(比如为了“先收一批再批量发”),结果发现 CPU 没干多少事,线程却频繁唤醒。这时应改用 BufferBlock 配合手动批处理逻辑(例如用 ReceiveAsync 循环 + 计时器或数量阈值)。
-
BufferBlock适合做「解耦生产/消费节奏」的中间缓存 -
TransformBlock必须有实际转换逻辑才体现价值,否则是冗余抽象 - 若需异步转换(如调用 HTTP API),必须用
TransformBlock并传入Func<TIn, Task<TOut>>,不能用同步委托
ExecutionDataflowBlockOptions.MaxDegreeOfParallelism 设成 1 和 -1 的真实区别
设为 1 表示严格串行:同一时间最多一个消息在执行委托,后续消息排队等待前一个完成;设为 -1 表示不限并发数 —— 但注意,这不等于“无限创建线程”,而是由 TaskScheduler 决定如何调度(默认是 ThreadPool,受线程池启发式算法控制)。
容易踩的坑是认为 -1 就能“压满 CPU”,结果在高吞吐下大量任务堆积,内存暴涨甚至 OutOfMemoryException。尤其当处理逻辑含 I/O 但没正确用 await(比如用了 .Result 或 .Wait()),会阻塞线程池线程,拖垮整个管道。
- IO 密集型处理(如 HTTP 请求、文件读写)建议显式设为较小值(如 4–16),避免耗尽线程池
- CPU 密集型且无锁共享状态时,可设为
Environment.ProcessorCount,但需配合Task.Run显式移出同步上下文 - 永远不要在
TransformBlock委托里调用.Wait()或访问.Result,这是死锁高发区
如何让多个 TransformBlock 串联后自动传播取消信号
关键不在块本身,而在链接方式:LinkTo 不传递 CancellationToken,必须在每个块初始化时显式传入 ExecutionDataflowBlockOptions 并设置 CancellationToken。即使上游块被取消,下游块也不会自动感知 —— 它们各自独立响应自己的 token。
典型错误是只给第一个块传 token,后面全用默认构造,导致取消后上游停了,下游还在空转或卡在未完成的异步操作上。
- 每个需要响应取消的
ITargetBlock或ISourceBlock都要单独配置CancellationToken - 如果使用
LinkTo的谓词重载(如block.LinkTo(target, x => x > 0)),谓词函数内不可依赖外部状态(如已取消的 token),因为谓词执行不保证与块生命周期同步 - 取消后调用
Complete()是好习惯,但必须确保所有块都已完成(可用Completion.ContinueWith链式等待)才能释放资源
为什么 ActionBlock<t></t> 收不到数据?常见链路断裂点
最常被忽略的是忘记调用 Complete() —— 数据流不会因为“没新数据来”就自动结束,上游块必须显式完成,下游才能收到“完成通知”并停止等待。另一个高频问题是 LinkTo 时没处理“未匹配到目标”的情况,默认会丢弃数据。
例如:source.LinkTo(sink, x => x.IsValid),当 x.IsValid == false 时,该消息直接消失,既不进 sink,也不报错,调试时极难察觉。
- 始终为
LinkTo添加“兜底目标”:source.LinkTo(sink, x => x.IsValid); source.LinkTo(DataflowBlock.NullTarget<T>()); - 上游块调用
Complete()后,记得 await 其Completion以确认所有数据已处理完毕 - 若用
Post发送数据但返回false,说明目标块已Complete或Faulted,此时应检查块状态而非重试
真正难的不是搭起管道,而是预判哪一环会成为瓶颈、哪个 token 会被忽略、哪条链接会在静默中丢失数据。TPL Dataflow 的弹性背后,藏着大量隐式契约,漏掉任意一条,都会让整条流水线在看似正常时突然失速或卡死。










