C# Task.Factory.StartNew()是否保证使用调用线程以外的其他线程?

C# Task.Factory.StartNew()是否保证使用调用线程以外的其他线程?,c#,multithreading,locking,task,task-parallel-library,C#,Multithreading,Locking,Task,Task Parallel Library,我正在从一个函数启动一个新任务,但我不希望它在同一个线程上运行。我不在乎它运行在哪个线程上,只要它是另一个线程(因此中给出的信息没有帮助) 我是否保证在允许Task t再次输入之前,以下代码将始终退出TestLock?如果没有,建议采用什么设计模式来防止再次出现 object TestLock = new object(); public void Test(bool stop = false) { Task t; lock (this.TestLock) {

我正在从一个函数启动一个新任务,但我不希望它在同一个线程上运行。我不在乎它运行在哪个线程上,只要它是另一个线程(因此中给出的信息没有帮助)

我是否保证在允许
Task t
再次输入之前,以下代码将始终退出
TestLock
?如果没有,建议采用什么设计模式来防止再次出现

object TestLock = new object();

public void Test(bool stop = false) {
    Task t;
    lock (this.TestLock) {
        if (stop) return;
        t = Task.Factory.StartNew(() => { this.Test(stop: true); });
    }
    t.Wait();
}
编辑:根据Jon Skeet和Stephen Toub的以下回答,确定防止重入的简单方法是传递CancellationToken,如此扩展方法所示:

public static Task StartNewOnDifferentThread(this TaskFactory taskFactory, Action action) 
 {
    return taskFactory.StartNew(action: action, cancellationToken: new CancellationToken());
}

关于这个问题,我给Stephen Toub(该组织的一名成员)发了邮件。他很快就回来了,有很多细节,所以我就把他的文字复制粘贴到这里。我没有全部引用,因为阅读大量引用的文本最终会比香草黑加白更不舒服,但事实上,这是斯蒂芬-我不知道这么多东西:)我在社区维基上做了这个回答,以反映下面所有的优点并不是我真正的内容:

如果调用已完成的,则不会有任何阻塞(如果任务以非
RanToCompletion
的方式完成,或以其他方式返回,则只会引发异常)。如果对已经在执行的任务调用
Wait()
,它必须阻塞,因为它无法合理地执行任何其他操作(当我说阻塞时,我包括了真正的基于内核的等待和旋转,因为它通常会同时执行这两种操作)。类似地,如果对已创建了
等待激活状态的任务调用
Wait()
,它将阻塞,直到任务完成。这些都不是正在讨论的有趣案例

有趣的情况是,当您对处于
WaitingToRun
状态的任务调用
Wait()
时,这意味着该任务先前已排队等待,但TaskScheduler尚未真正运行任务的委托。在这种情况下,调用
Wait
将通过调用调度程序的
TryExecuteTaskInline
方法,询问调度程序是否可以在当前线程上运行该任务。这称为内联。调度程序可以选择通过调用
base.TryExecuteTask
内联任务,也可以返回“false”以指示它没有执行任务(这通常是通过诸如

return SomeSchedulerSpecificCondition() ? false : TryExecuteTask(task);
TryExecuteTask
返回布尔值的原因是它处理同步以确保给定任务只执行一次)。因此,如果调度器想要在
等待期间完全禁止任务的内联,它可以实现为
返回false如果计划程序希望尽可能始终允许内联,则可以将其实现为:

return TryExecuteTask(task);
在当前的实现中(包括.NET 4和.NET 4.5,我个人不认为这会改变),如果当前线程是线程池线程,并且该线程是之前排队等待任务的线程,则针对线程池的默认调度程序允许内联

请注意,这里没有任意可重入性,因为默认调度程序在等待任务时不会泵送任意线程。。。它只允许内联该任务,当然,任何内联该任务反过来决定执行。还要注意的是,
Wait
在某些情况下甚至不会询问调度程序,而是宁愿阻塞。例如,如果您传入一个可取消的,或者传入一个非无限超时,它将不会尝试内联,因为内联任务的执行可能需要任意长的时间,这可能会导致取消请求或超时显著延迟。总的来说,TPL试图在浪费执行
等待的线程和过度重用该线程之间找到一个合适的平衡点。这种内联对于递归分而治之问题(例如)非常重要,在递归分而治之问题中,您生成多个任务,然后等待它们全部完成。如果在没有内联的情况下执行这些操作,那么在耗尽池中的所有线程以及它希望提供给您的任何未来线程时,您很快就会死锁

Wait
不同的是,如果正在使用的调度程序选择作为QueueTask调用的一部分同步运行任务,则调用也可能(远程)在此时执行任务。NET中内置的调度器都不会这样做,我个人认为这对调度器来说是个糟糕的设计,但理论上是可能的,例如:

protected override void QueueTask(Task task, bool wasPreviouslyQueued)
{
    return TryExecuteTask(task);
}
Task.Factory.StartNew
的重载不接受
TaskScheduler
使用
TaskFactory
中的调度程序,在
Task.Factory
的情况下,该调度程序以
TaskScheduler.Current
为目标。这意味着,如果您从队列中调用
Task.Factory.StartNew
,它也会队列中调用
RunSynchronouslyTaskScheduler
,从而导致
StartNew
调用同步执行任务。如果您对此有任何顾虑(例如,您正在实现一个库,但不知道将从何处调用您),您可以显式地将
TaskScheduler.Default
传递到
StartNew
调用,使用
Task.Run
(始终转到
TaskScheduler.Default
),或者使用为目标任务调度器创建的
任务工厂
。默认值


编辑:好吧,看起来我完全错了,当前正在等待任务的线程可能被劫持。下面是发生这种情况的一个简单示例:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1 {
    class Program {
        static void Main() {
            for (int i = 0; i < 10; i++)
            {
                Task.Factory.StartNew(Launch).Wait();
            }
        }

        static void Launch()
        {
            Console.WriteLine("Launch thread: {0}", 
                              Thread.CurrentThread.ManagedThreadId);
            Task.Factory.StartNew(Nested).Wait();
        }

        static void Nested()
        {
            Console.WriteLine("Nested thread: {0}", 
                              Thread.CurrentThread.ManagedThreadId);
        }
    }
}
正如你所看到的,在那里
Launch thread: 3
Nested thread: 3
Launch thread: 3
Nested thread: 3
Launch thread: 3
Nested thread: 3
Launch thread: 3
Nested thread: 3
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
  Task<TResult> task = Task.Run(func);
  task.Wait(TimeSpan.FromHours(1)); // Whatever is enough for task to start
  return task.Result;