在哪个调度程序 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()任务是否保证在我的计划程序上运行?如果没有,它将使用哪个调度程序?

在哪个调度程序 Task.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 是为了ContinueWithTask.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();
}

请注意,outerTask<Task>,因此有outer.Unwrap()。你也可以做outer.Result.Wait(),但有一些语义上的差异,特别是如果你使用outer.Start(scheduler)而不是outer.RunSynchronously(scheduler)