具有永久任务/线程的 TPL 数据流块
Posted
技术标签:
【中文标题】具有永久任务/线程的 TPL 数据流块【英文标题】:TPL Dataflow Block with permanent Task/Thread 【发布时间】:2017-11-11 22:06:59 【问题描述】:Stepen Toub 在Channel 9 Video 中提到,如果将项目推送到其传入队列,*Block
会创建一个任务。如果队列中的所有项目都计算完毕,则任务将被销毁。
如果我使用很多块来构建网格,那么实际运行的任务的数量并不清楚(如果TaskScheduler
是默认值,那么活动ThreadPool
线程的数量也不清楚)。
TPL Dataflow
是否提供了一种我可以说的方式:“好的,我想要这种带有永久运行任务(线程)的块?
【问题讨论】:
【参考方案1】:TL;DR:没有办法将线程专用于块,因为它显然与 TPL Dataflow
的目的相冲突,除非实现您自己的 TaskScheduler
。在尝试提高应用程序性能之前进行测量。
我刚刚看了视频,找不到这样的短语:
如果项目被推送到其传入队列,则创建一个任务。如果队列中的所有项目都计算完毕,则任务将被销毁。
也许我遗漏了一些东西,但斯蒂芬所说的只是:[开头]我们有一个常见的Producer-Consumer
问题,可以用 .Net 4.0 堆栈轻松实现,但问题是如果数据用完,消费者离开循环,永远不会返回。
[在那之后]斯蒂芬解释了如何用TPL Dataflow
解决这样的问题,他说ActionBlock
开始Task
如果没有开始。在该任务中,有代码等待(以async
方式)接收新消息,释放线程,但不破坏任务。
Stephen 在解释跨链接块发送消息时还提到了任务,他说如果没有数据要发送,posting 任务将消失。并不是说block对应的task消失了,只是某个子task被用来发送数据而已。
在TPL Dataflow
中,向块说不会有更多数据的唯一方法是:调用它的Complete
方法或完成任何链接块。在该消费任务将停止之后,并且在处理完所有缓冲数据之后,该块将结束它的任务。
根据TPL Dataflow
的官方 github,块内消息处理的所有任务都创建为DenyChildAttach
,有时还带有PreferFairness
标志。所以,我没有理由提供一种机制让一个线程直接适合块,因为如果块没有数据,它会卡住并浪费 CPU 资源。您可能会为块引入一些自定义TaskScheduler
,但现在还不清楚为什么需要它。
如果您担心某些块可能比其他块获得更多的 CPU 时间,有一种方法可以利用这种效果。根据official docs,您可以尝试设置MaxMessagesPerTask
属性,在发送一定数量的数据后强制任务重新启动。不过,这应该仅在测量实际执行时间后进行。
现在,回到你的话:
实际运行的任务数量不清楚 活跃的ThreadPool线程数也不清楚
您是如何分析您的应用程序的?在调试过程中,您可以轻松找到all active tasks 和all active threads。如果这还不够,您可以使用本地 Microsoft 工具或专门的分析器(例如 dotTrace)来分析您的应用程序。这样的工具包可以很容易地为您提供有关您的应用程序正在发生的事情的信息。
【讨论】:
感谢超级详细的回答。现在事情对我来说更清楚了。【参考方案2】:The talk 是关于 TPL 数据流库的内部机制。作为一种机制,它非常有效,除非您的预期吞吐量约为每秒 100,000 条消息或更多(在这种情况下,您应该寻找将工作负载分块的方法),否则您不应该真正担心任何开销。即使对于粒度非常小的工作负载,使用单个任务处理所有消息或处理每个消息的专用任务之间的差异应该几乎不明显。 Task
是一个“重”通常为几百字节的对象,.NET 平台每秒能够创建和回收数百万个这种大小的对象。
如果每个Task
都需要自己专用的1MB 线程才能运行,那将是一个问题,但事实并非如此。通常,这些任务使用ThreadPool
线程执行,单个ThreadPool
线程可能每秒执行数百万个短期任务。
我还应该提到,TPL 数据流也支持异步 lambda(返回类型为 Task
的 lambda),在这种情况下,这些块基本上不需要执行任何代码。它们只是等待生成的 promise 样式的任务完成,而对于异步等待 no thread 是必需的。
【讨论】:
以上是关于具有永久任务/线程的 TPL 数据流块的主要内容,如果未能解决你的问题,请参考以下文章
TPL Dataflow LinkTo TransformBlock 非常慢