在哪个调度程序 Task.ContinueWith() 上运行
本文关键字:运行 ContinueWith Task 调度程序 | 更新日期: 2023-09-27 18:00:28
请考虑以下代码:
// MyKickAssTaskScheduler is a TaskScheduler, IDisposable
using (var scheduler = new MyKickAssTaskScheduler())
{
Task foo = new Task(() => DoSomething());
foo.ContinueWith((_) => DoSomethingElse());
foo.Start(scheduler);
foo.Wait();
}
ContinueWith()
任务是否保证在我的计划程序上运行?如果没有,它将使用哪个调度程序?
StartNew, ContinueWith 将默认为 TaskScheduler.Current, Current 将返回默认调度程序, 当未从任务 (MSDN( 中调用时。
若要避免默认调度程序问题,应始终将显式 TaskScheduler 传递给 Task.ContinueWith 和 Task.Factory.StartNew。
继续是危险的
ContinueWith(( 任务是否保证在我的调度程序上运行?如果没有, 它将使用哪个调度程序?
不,它将使用传递给原始Task
的调度程序。 ContinueWith
将默认使用 TaskScheduler.Current
,在这种情况下是默认的线程池任务调度程序。您提供的 task.Start
上下文与延续中使用的上下文之间没有传播
从源头:
public Task ContinueWith(Action<Task> continuationAction)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
return ContinueWith(continuationAction,
TaskScheduler.Current,
default(CancellationToken),
TaskContinuationOptions.None, ref stackMark);
}
@Noseratio - 阅读它,但仍然对此的有效性持怀疑态度 行为 - 我在非默认调度程序上运行了第一个任务 原因。为什么TPL决定延续,这总是 与我的任务顺序,应该在另一个任务上运行吗?
我同意 - 这不是最好的设计 - 但我认为默认为 TaskScheduler.Current
是为了ContinueWith
与 Task.Factory.StartNew
保持一致, 首先也默认为 TaskScheduler.Current
。Stephen Toub确实解释了后一种设计决策:
在许多情况下,这是正确的行为。 例如,假设 你正在实现一个递归分而治之的问题,你 有一个任务应该处理一些工作块,并且它在 turn 细分其工作并安排任务来处理这些块。 如果该任务在代表特定池的计划程序上运行 线程数,或者它是否在具有并发性的调度程序上运行 限制,依此类推,您通常需要它随后创建的任务 也在同一调度程序上运行。
因此,ContinueWith
使用您调用ContinueWith
时当前正在执行的任何任务的当前(环境(TaskScheduler.Current
,而不是先前任务之一。如果这对您来说是一个问题,并且您无法显式指定任务计划程序,则有一种解决方法。您可以将自定义任务计划程序设置为特定范围的环境计划程序,如下所示:
using (var scheduler = new MyKickAssTaskScheduler())
{
Task<Task> outer = new Task(() =>
{
Task foo = new Task(() => DoSomething());
foo.ContinueWith((_) => DoSomethingElse());
foo.Start(); // don't have to specify scheduler here
return foo;
}
outer.RunSynchronously(scheduler);
outer.Unwrap().Wait();
}
请注意,outer
是Task<Task>
,因此有outer.Unwrap()
。你也可以做outer.Result.Wait()
,但有一些语义上的差异,特别是如果你使用outer.Start(scheduler)
而不是outer.RunSynchronously(scheduler)
。