【问题标题】:Throttling Tasks competing for execution (under the hood)限制竞争执行的任务(在引擎盖下)
【发布时间】:2013-03-05 20:29:20
【问题描述】:

考虑一个 Microsoft .NET Framework 应用程序,它充分利用 async/await 工具来生成许多同时(或嵌套)任务。没有显着的“流”控制,即任务大多是独立的,不会相互等待完成,因此都在竞争执行。

决定如何/何时调度或执行任务以及在哪个线程上执行任务的框架组件是什么?

有哪些可能的课程可以通过用户代码影响该机制?

编辑
found 一个很好的句子指出了我的问题的目标:

每当代码等待一个等待者说它尚未完成的等待对象时(即等待者的 IsCompleted 返回 false),该方法需要暂停 ...

这里描述的时间点比我的问题晚了一步 - 有些东西已经处理了用户代码,构造了 awaitableawaiter,可能捕获了上下文并决定哪个线程将执行(或正在执行)该任务。我在问那个“东西”。

我确实看到了用户代码如何影响框架采用的路线,但这些都是程序员的外部“受过教育”决定。假设我们采取了我们可以影响的每一条可能的路线并使其过度饱和......我们正在努力打击一些设施,对吗?也许没有一句话的答案......如果我在下面@Stephen 的回答中看到它的速度很慢,我很抱歉 - 感谢您的帮助并继续挖掘。

(一些相关主题似乎在同步/等待抽象下更深入,如线程池、上下文。我应该朝那个方向挖掘吗?)
结束编辑

自定义 TaskScheduler 是(最佳)(唯一)答案吗?

就问题而言,我忽略了任何外部限制因素,例如资源匮乏(网络、I/O 饱和)或业务原因(人为限制)。或者通过尝试跟踪正在执行的内容等来破解用户代码节流。

【问题讨论】:

  • TaskShedulers 对于限制 async Tasks 几乎没有用(除非您只想限制非异步部分)。您能否详细解释一下您要做什么以及为什么要限制Tasks?
  • @svick,我将用户代码视为产生任务(运行它们的请求)的泵。框架中的某些东西正在做出“我将在同一个线程上运行此任务,因为...”或“我将生成一个线程,因为...”的决定。如果你发现我的想法有缺陷——这就是我想要解决的问题——我正在努力学习——少想象,多了解。
  • 关于您的编辑:每个async 方法都开始同步执行;它只有在遇到第一个await(其中!IsCompleted)时才变为异步。因此,要安排方法的第一部分,只需使用 TaskFactory 和自定义 TaskScheduler
  • 听起来您真正想要的是一些介绍性的async 材料。我建议阅读(按顺序):my introMSDN overviewTAP documentFAQ
  • @Stephen - 感谢关于“RE:您的编辑”评论 - 所以引擎盖下的“事情”,即“第一个 await 是 TaskFactory 实现?你能归零吗?在特定方法或方法类上?

标签: .net async-await


【解决方案1】:

同时(也与父子相关的)任务。

当一个async 方法调用另一个方法时,这些任务可以被认为是一种“父/子”关系。但是,从技术上讲,没有parent/child task relationship

控制调度、线程、执行的框架组件是什么?

这在MSDN docs (under "Suspending Execution with Await")my async/await intro 中有描述。当async 方法通过await 挂起自身时,默认情况下它将捕获当前SynchronizationContext(或当前TaskScheduler,如果没有当前SynchronizationContext)并使用它来恢复该方法。这是默认行为;任何async 方法都可以通过awaiting ConfigureAwait(false) 的结果选择not 来恢复其捕获的上下文。在绝大多数情况下,这将在线程池上执行该方法的延续。

您应该考虑的第一件事是在任何可能的地方申请ConfigureAwait(false)。如果您始终如一地执行此操作,则您的延续大部分由线程池管理,从而可以最佳地利用可用资源。手动限制应该没有必要。

也就是说,有几种方法可以做到这一点。

任务调度器

您可以使用TaskScheduler 来控制async 延续的执行(只要它们使用ConfigureAwait(false))。 ConcurrentExclusiveSchedulerPair can be used for throttling or mutual exclusion 在这个调度级别。然而,正如@svick 指出的那样,任务调度程序只能“看到”每个async 方法的单个(同步)部分。所以这通常不是人们正在寻找的解决方案。

TPL 数据流

如果您主要对节流粗粒度操作(可以根据需要有许多“子”操作)感兴趣,您可以使用来自TPL DataflowActionBlock 来做到这一点:

var block = new ActionBlock<Func<Task>>(f => f(),
    new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = ...; });
block.Post(MyMethodAsync);
...

TaskScheduler 方法不同,TPL Dataflow 的MaxDegreeOfParallelism 确实将async 方法视为一个整体,包括其所有“子”任务和延续。

一旦您开始使用 TPL Dataflow,您可能会发现应用程序逻辑的其他部分也可以更自然地表达出来。

异步信号量

如果您的节流需求更复杂,另一种方法是AsyncSemaphoreavailable in my AsyncEx library。您需要限制的每个async 方法将使用await WaitAsync 开始执行并以Release 完成。通过将这些调用放在您选择的位置,您可以完全控制限制的范围。

【讨论】:

  • 我编辑了这个问题——并不是指这样的父子,而是任务的嵌套。反正不是那么相关。请参阅我对 svick 的评论 RE:我的问题的性质的评论。我可以看到许多通过使用专门的 TaskScheduler、您的 TPL 数据流参考等来进行节流的参考,但这些总是由程序员的决定支持的。我的问题寻求有助于做出这种决定的理解和知识。非常小的例子——我可以设置并行度很好,但是有什么价值呢?我已经采取了从头开始研究的方法。
  • 请参阅我的答案的第一部分,了解async 如何与调度原语和线程池一起使用。
猜你喜欢
  • 1970-01-01
  • 2011-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多