Task.ContinueWith() 在哪个调度程序上运行?

On which scheduler Task.ContinueWith() runs?

考虑以下代码:

// MyKickAssTaskScheduler is a TaskScheduler, IDisposable
using (var scheduler = new MyKickAssTaskScheduler())
{
    Task foo = new Task(() => DoSomething());
    foo.ContinueWith((_) => DoSomethingElse());
    foo.Start(scheduler);
    foo.Wait();
}

ContinueWith() 任务是否保证 运行 在我的调度程序上?如果不是,它将使用哪个调度程序?

Is the ContinueWith() Task guaranteed to run on my scheduler? If not, which scheduler will it use?

不,它将使用传递给原始 Task 的调度程序。 ContinueWith 将默认使用 TaskScheduler.Current,在这种情况下是默认的线程池任务调度程序。您提供的 task.Start 上下文与 continuation

中使用的上下文之间没有传播

From the source:

public Task ContinueWith(Action<Task> continuationAction)
{
    StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
    return ContinueWith(continuationAction, 
                        TaskScheduler.Current, 
                        default(CancellationToken),
                        TaskContinuationOptions.None, ref stackMark);
}

StartNew、ContinueWith 将默认为 TaskScheduler.Current,Current 将 return 默认调度程序,当未从任务 (MSDN) 中调用时。

为避免默认调度程序问题,您应该始终将显式 TaskScheduler 传递给 Task.ContinueWith 和 Task.Factory.StartNew。

ContinueWith is Dangerous

@Noseratio - read it, but still skeptic about the validity of this behavior - I ran the first task on a non-default scheduler for a reason. Why did TPL decide the continuation, which is always sequential to my task, should run on another?

我同意 - 这不是最好的设计 - 但我认为默认为 TaskScheduler.Current 是为了 ContinueWithTask.Factory.StartNew 一致,默认为 [=11] =] 也是,首先。 Stephen Toub does explain 后一个设计决定:

In many situations, that’s the right behavior. For example, let’s say you’re implementing a recursive divide-and-conquer problem, where you have a task that’s supposed to process some chunk of work, and it in turn subdivides its work and schedules tasks to process those chunks. If that task was running on a scheduler representing a particular pool of threads, or if it was running on a scheduler that had a concurrency limit, and so on, you’d typically want those tasks it then created to also run on the same scheduler.

因此,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)