我就这个问题向PFX 团队的成员 Stephen Toub 发送了邮件。他很快就回来找我了,提供了很多细节——所以我就在这里复制并粘贴他的文字。我没有全部引用,因为阅读大量引用的文本最终会比普通的黑白黑白更舒服,但实际上,这是斯蒂芬 - 我不知道这么多东西 :) 我做了这个答案社区 wiki 以反映以下所有优点并不是我真正的内容:
如果您调用已完成Wait()
的任务,则不会有任何阻塞(如果任务以非 的 TaskStatus 完成,它只会抛出异常,否则RanToCompletion
返回为nop)。如果你调用Wait()
一个已经在执行的任务,它必须阻塞,因为它不能合理地做任何其他事情(当我说阻塞时,我包括真正的基于内核的等待和旋转,因为它通常会混合使用两者)。同样,如果您调用Wait()
具有Created
orWaitingForActivation
状态的任务,它将阻塞,直到任务完成。这些都不是正在讨论的有趣案例。
有趣的情况是,当您调用Wait()
处于WaitingToRun
状态的 Task 时,这意味着它之前已排队到TaskScheduler,但 TaskScheduler 尚未开始实际运行 Task 的委托。在这种情况下,调用将询问调度程序是否可以通过调用调度程序的方法Wait
在当前线程上运行任务。TryExecuteTaskInline
这称为内联。调度程序可以选择通过调用来内联任务base.TryExecuteTask
,或者它可以返回“false”以指示它没有执行任务(通常这是通过类似...的逻辑来完成的)
return SomeSchedulerSpecificCondition() ? false : TryExecuteTask(task);
返回布尔值的原因TryExecuteTask
是它处理同步以确保给定的任务只执行一次)。因此,如果调度程序希望在 期间完全禁止内联任务Wait
,则可以将其实现为return false;
如果调度程序希望尽可能始终允许内联,则可以将其实现为:
return TryExecuteTask(task);
在当前实现中(.NET 4 和 .NET 4.5,我个人不希望这会改变),如果当前线程是 ThreadPool 线程并且该线程是一个先前已将任务排队。
请注意,这里没有任意重入,因为默认调度程序在等待任务时不会抽出任意线程......它只允许内联该任务,当然任何内联该任务依次决定去做。另请注意,Wait
在某些情况下甚至不会询问调度程序,而是更愿意阻止。例如,如果您传入一个可取消的CancellationToken,或者如果您传入一个非无限超时,则它不会尝试内联,因为内联任务的执行可能需要任意长的时间,即全有或全无,这最终可能会显着延迟取消请求或超时。总的来说,TPL 试图在浪费线程之间取得不错的平衡。Wait
'ing 并重复使用该线程太多。这种内联对于递归的分而治之问题(例如QuickSort)非常重要,在这种问题中,您会产生多个任务,然后等待它们全部完成。如果在没有内联的情况下这样做,那么当您耗尽池中的所有线程以及它想要给您的任何未来线程时,您很快就会陷入僵局。
与 分开,如果正在使用的调度程序选择作为 QueueTask 调用的一部分同步运行任务Wait
,则Task.Factory.StartNew调用也有可能最终执行任务。.NET 中内置的调度程序都不会这样做,我个人认为这对调度程序来说是一个糟糕的设计,但理论上是可能的,例如:
protected override void QueueTask(Task task, bool wasPreviouslyQueued)
{
return TryExecuteTask(task);
}
that的重载Task.Factory.StartNew
不接受 aTaskScheduler
使用来自的调度程序,在targetsTaskFactory
的情况下。这意味着如果您从排队到这个神话的任务中调用,它也会排队到,导致调用同步执行任务。如果您对此完全担心(例如,您正在实现一个库并且您不知道将从哪里调用您),您可以显式传递给调用,使用(总是转到),或使用created 来定位.Task.Factory
TaskScheduler.Current
Task.Factory.StartNew
RunSynchronouslyTaskScheduler
RunSynchronouslyTaskScheduler
StartNew
TaskScheduler.Default
StartNew
Task.Run
TaskScheduler.Default
TaskFactory
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
如您所见,有很多次等待线程被重用于执行新任务。即使线程已获得锁,这种情况也可能发生。讨厌的重入。我很震惊和担心:(