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;