C# 在哪个计划程序任务.ContinueWith()上运行?

C# 在哪个计划程序任务.ContinueWith()上运行?,c#,task-parallel-library,task,C#,Task Parallel Library,Task,考虑以下代码: // MyKickAssTaskScheduler is a TaskScheduler, IDisposable using (var scheduler = new MyKickAssTaskScheduler()) { Task foo = new Task(() => DoSomething()); foo.ContinueWith((_) => DoSomethingElse()); foo.Start(scheduler);

考虑以下代码:

// MyKickAssTaskScheduler is a TaskScheduler, IDisposable
using (var scheduler = new MyKickAssTaskScheduler())
{
    Task foo = new Task(() => DoSomething());
    foo.ContinueWith((_) => DoSomethingElse());
    foo.Start(scheduler);
    foo.Wait();
}
ContinueWith()
任务是否保证在我的计划程序上运行?如果没有,它将使用哪个计划程序

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

否,它将使用传递给原始
任务的调度程序
ContinueWith
将默认使用
TaskScheduler.Current
,在这种情况下是默认的线程池任务计划程序。您提供的
任务.Start的上下文与continuation中使用的上下文之间没有传播

:

public Task ContinueWith(Action continuationAction)
{
stackcrawmark stackMark=stackcrawmark.LookForMyCaller;
返回ContinueWith(continuationAction,
TaskScheduler.Current,
默认(取消令牌),
TaskContinuationOptions.None,参考stackMark);
}

StartNew,ContinueWith将默认为TaskScheduler。Current,Current将在未从任务(MSDN)中调用时返回默认计划程序

要避免默认计划程序问题,应始终将显式TaskScheduler传递给Task.ContinueWith和Task.Factory.StartNew


@阅读它,但仍然怀疑它的有效性 行为-我在一个非默认调度程序上运行了第一个任务 原因。为什么第三方物流决定继续,这一直是一个问题 按照我的任务顺序,应该在另一个上运行吗

我同意-这不是最好的设计-但我认为默认为
TaskScheduler.Current
是为了
ContinueWith
Task.Factory.StartNew
保持一致,后者默认为
TaskScheduler.Current
。Stephen Toub后一个设计决策:

在许多情况下,这是正确的行为。比如说 您正在实现一个递归的分而治之问题 有一个任务,它应该处理一些大块的工作,并在 turn细分其工作并安排任务来处理这些块。 如果该任务在代表特定池的计划程序上运行 线程数,或者如果它运行在具有并发性的调度程序上 限制等等,您通常希望它随后创建的任务 也在同一个调度程序上运行

因此,
ContinueWith
使用当前(环境)
TaskScheduler。调用
ContinueWith
时当前正在执行的任何任务的当前
,而不是先前任务的当前
。如果这对您来说是一个问题,并且您无法显式指定任务调度器,那么有一个解决方法。您可以将自定义任务计划程序设置为特定范围的环境任务计划程序,如下所示:

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();
}
使用(var调度程序=新的MyKickAssTaskScheduler())
{
任务外部=新任务(()=>
{
任务foo=新任务(()=>DoSomething());
foo.ContinueWith(()=>DoSomethingElse());
foo.Start();//不必在此处指定调度程序
返回foo;
}
同步运行(调度程序);
外部。展开()。等待();
}

请注意,
outer
Task
,因此有
outer.Unwrap()
。您也可以执行
outer.Result.Wait()
,但有一些语义差异,特别是如果您使用
outer.Start(调度程序)
而不是
outer.RunSynchronously(调度程序)的话

我运行了一些测试,发现我的延续程序在不同的计划程序上运行。我推测这是因为TaskScheduler.Current在从任务中调用时返回默认的.Net Framework计划程序。请参阅我的自定义计划程序是的变体,因此使用的STA线程不是来自该线程的pool@bavaza你说得对,我做了一些测试,调度程序没有流动。@YuvalItzchakov,但它是环境任务调度程序:)@YuvalItzchakov,它是运行当前正在执行的任务的任务调度程序。因此,对于在该任务中通过TPL API创建的任何任务,它都会成为环境,您不需要明确指定调度程序(除了
task.Run
,它总是使用
TaskScheduler.Default
)。请看我答案中的代码。谢谢。这是一个有问题的设计决策还是我遗漏了什么?TPL有人解释过吗?@bavaza-我的新朋友也开始阅读,以获得关于TPL的深入信息it@bavaza,TPL的Stephen Toub在这里提供了一些解释:@Noseratio-阅读它,但仍然怀疑这种行为的有效性-我在非默认调度程序上运行第一个任务是有原因的。为什么TPL决定在另一个任务上运行延续,这总是与我的任务顺序一致?@bavaza,我回答了你的评论。酷把戏:)。尽管如此,我认为我还是会坚持明确指定调度程序,或者完全避免
ContinueWith()
。@bavaza,如果您可以完全避免
ContinueWith
,而使用
wait
,那将是最好的决定。您可以使用自定义的
SynchronizationContext
(这将使
wait
-友好)围绕线程对调度程序进行建模,只需执行
TaskScheduler.FromCurrentSynchronizationContext
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();
}