考虑一个Microsoft .NET Framework应用程序,它充分利用async / await工具来生成许多同时(或嵌套)任务。没有重要的“流量”控制,即任务大部分是独立的,没有等待彼此完成,因此所有人都在竞争执行。
什么是框架组件决定如何/何时安排或执行任务以及在哪个线程上?
从用户代码中影响该机制的可能课程有哪些?
的修改
我found这是一个很好的句子,它针对我的问题所针对的地方:
每当代码等待等待其等待者说它尚未完成时(即等待者的IsCompleted返回false),该方法需要暂停......
这里描述的时间点是我的问题后面的一步 - 有些事情已经处理了用户代码,构建了 awaitable 和 awaiter ,可能捕获了上下文并决定哪个线程将执行(或正在执行)任务。我在问这个“东西”。
我确实看到用户代码如何影响框架所采用的路径,但这些是程序员的外部“受过教育”的决策。假设我们采取了我们可以影响的每条可能的路线并使其过度饱和......我们正在努力寻找一些设施,对吧?也许没有一句话的答案......我很抱歉,如果我在以下@ Stephen的答案中看到它很慢 - 我感谢你的帮助并继续挖掘。
(某些相关主题似乎在同步/等待摘要下更深入,如线程池,上下文。我是否要朝那个方向挖掘?)
结束编辑
自定义TaskScheduler是(最佳)(仅)答案吗?
出于问题的目的,我忽略了限制的任何外部考虑因素,如资源匮乏(网络,I / O饱和度)或业务原因(人为限制)。或者通过尝试跟踪正在执行的内容等来破解用户代码限制。
答案 0 :(得分:2)
同时(也是与父子相关的)任务。
当一个async
方法调用另一个方法时,这些任务可以被认为是“父/子”关系。但从技术上讲,没有parent/child task relationship。
控制调度,线程,执行的框架组件是什么?
MSDN docs (under "Suspending Execution with Await")以及my async
/await
intro中对此进行了描述。当async
方法通过await
暂停时,默认情况下,如果没有当前SynchronizationContext
,它将捕获当前TaskScheduler
(或当前SynchronizationContext
)并使用它来恢复该方法。这是默认行为;任何async
方法都可以通过await
ConfigureAwait(false)
的结果选择 not 来恢复其捕获的上下文。在绝大多数情况下,这将在线程池上执行方法的继续。
您应该考虑的第一件事是尽可能地应用ConfigureAwait(false)
。如果一致地执行此操作,则您的延续大部分由线程池管理,这可以最佳地利用可用资源。手动节流不应该是必要的。
也就是说,有几种方法可以做到。
<强>的TaskScheduler 强>
您可以使用TaskScheduler
来控制async
续集的执行(只要他们不使用ConfigureAwait(false)
)。 ConcurrentExclusiveSchedulerPair
can be used for throttling or mutual exclusion在此级别的日程安排中。但是,正如@svick指出的那样,任务调度程序只“看到”每个async
方法的各个(同步)部分。所以这通常不是人们正在寻找的解决方案。
TPL数据流
如果您主要对限制粗粒度操作感兴趣(可以根据需要进行多次“子”操作),则可以使用TPL Dataflow中的ActionBlock
来执行此操作:
var block = new ActionBlock<Func<Task>>(f => f(),
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = ...; });
block.Post(MyMethodAsync);
...
与TaskScheduler
方法不同,TPL数据流的MaxDegreeOfParallelism
确实将async
方法视为一个整体,包括其所有“子”任务和延续。
一旦开始使用TPL Dataflow,您可能会发现应用程序逻辑的其他部分也更自然地用它来表达。
<强> AsyncSemaphore 强>
如果您的限制需求更复杂,则另一种方法是AsyncSemaphore
,available in my AsyncEx library。您需要限制的每个async
方法都会以await WaitAsync
开始执行,并以Release
结束。通过将这些调用放在您选择的位置,您可以完全控制受限制的范围。