0

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

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

从用户代码影响该机制的可能课程是什么?

编辑
找到了一个很好的句子,它指出了我的问题所针对的地方:

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

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

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

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

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

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

4

1 回答 1

2

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

当一个async方法调用另一个方法时,这些任务可以被认为是“父/子”关系。但是,从技术上讲,不存在父/子任务关系

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

这在MSDN 文档(在“Suspending Execution with Await”下)以及my async/ awaitintro中进行了描述。当一个async方法通过 挂起自己时await,默认情况下它将捕获当前SynchronizationContextTaskScheduler如果没有 current ,则为当前SynchronizationContext)并使用它来恢复该方法。这是默认行为;任何async方法都可以选择通过awaitConfigureAwait(false). 在绝大多数情况下,这将在线程池上执行该方法的延续。

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

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

任务调度器

您可以使用 aTaskScheduler来控制async延续的执行(只要它们使用ConfigureAwait(false))。可用于此调度级别的ConcurrentExclusiveSchedulerPair节流或互斥。但是,正如@svick 指出的那样,任务调度程序只能“看到”每个方法的各个(同步)部分。async因此,这通常不是人们正在寻找的解决方案。

TPL 数据流

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

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

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

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

异步信号量

如果您的节流需求更复杂,我的 AsyncEx 库中提供了AsyncSemaphore另一种方法。然后,您需要限制的每个方法都将开始执行并以. 通过将这些调用放在您选择的位置,您可以完全控制限制的范围。asyncawait WaitAsyncRelease

于 2013-03-17T14:54:50.193 回答