Erw*_*yer 74 c# multithreading locking task task-parallel-library
我从一个函数开始一个新的任务,但我不希望它在同一个线程上运行.我不关心它运行在哪个线程上,只要它是一个不同的线程(因此这个问题中给出的信息没有帮助).
我保证TestLock在允许Task t再次输入之前,以下代码将始终退出吗?如果没有,建议的设计模式是什么来防止再次出现?
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();
}
Run Code Online (Sandbox Code Playgroud)
编辑:基于Jon Skeet和Stephen Toub的以下答案,确定性地防止重入的一种简单方法是传递CancellationToken,如此扩展方法所示:
public static Task StartNewOnDifferentThread(this TaskFactory taskFactory, Action action)
{
return taskFactory.StartNew(action: action, cancellationToken: new CancellationToken());
}
Run Code Online (Sandbox Code Playgroud)
Jon*_*eet 83
我邮寄了Stephen Toub-- PFX团队的成员- 关于这个问题.他很快就回到了我身边,带着很多细节 - 所以我只是复制并粘贴他的文字.我没有引用它,因为阅读大量的引用文本最终变得不如香草黑白色,但真的,这是斯蒂芬 - 我不知道这么多东西:)我做了这个回答社区维基反映下面所有的好处并不是我的内容:
如果您调用已完成
Wait()的任务,则不会有任何阻塞(如果任务使用TaskStatus以外的任务完成RanToCompletion,或者以nop形式返回,则只会抛出异常).如果你调用Wait()一个已经在执行的任务,它必须阻塞,因为它没有其他任何可以合理做的事情(当我说阻止时,我包括真正的基于内核的等待和旋转,因为它通常会混合使用两者).同样,如果您调用Wait()具有Created或者WaitingForActivation状态的任务,它将阻塞,直到任务完成.这些都不是正在讨论的有趣案例.有趣的情况是当你调用
Wait()状态中的Task时WaitingToRun,意味着它之前已经排队到TaskScheduler,但是TaskScheduler还没有实际运行Task的委托.在这种情况下,调用Wait将询问调度程序是否可以通过调用调度程序的TryExecuteTaskInline方法在当前线程上运行任务.这称为内联.调度程序可以选择通过调用来内联任务base.TryExecuteTask,或者它可以返回'false'以指示它没有执行任务(通常这是通过像...这样的逻辑来完成的.Run Code Online (Sandbox Code Playgroud)return SomeSchedulerSpecificCondition() ? false : TryExecuteTask(task);
TryExecuteTask返回布尔值的原因是它处理同步以确保给定的任务只执行一次).因此,如果调度程序想要完全禁止在此期间内联任务Wait,则可以将其实现为return false;如果调度程序希望始终允许内联,则可以将其实现为:Run Code Online (Sandbox Code Playgroud)return TryExecuteTask(task);在当前的实现中(.NET 4和.NET 4.5,我个人并不希望这会改变),以ThreadPool为目标的默认调度程序允许内联当前线程是否为ThreadPool线程,如果该线程是一个先前排队的任务.
请注意,这里没有任意的重入,因为默认调度程序在等待任务时不会抽取任意线程...它只允许内联任务,当然任何内联任务依次决定去做.另请注意,
Wait在某些条件下甚至不会询问调度程序,而是更喜欢阻止.例如,如果传入可取消的CancellationToken,或者传入非无限超时,它将不会尝试内联,因为它可能需要任意长的时间来内联任务的执行,这是全部或全部,这最终可能会大大延迟取消请求或超时.总的来说,TPL试图在浪费正在进行的Wait线程和重复使用该线程之间取得相当大的平衡.这种内联对于递归分治问题(例如QuickSort)非常重要,在这些问题中,您生成多个任务,然后等待它们全部完成.如果这样做没有内联,那么当你耗尽池中的所有线程以及它想要给你的任何未来线程时,你很快就会陷入僵局.从不同的
Wait,这也是(远程)可能是Task.Factory.StartNew呼叫最终可能执行的任务,然后有,当且仅当所使用的调度选择同步为QueueTask调用的一部分运行任务..NET内置的调度程序都不会这样做,我个人认为这对调度程序来说是一个糟糕的设计,但理论上它是可行的,例如:Run Code Online (Sandbox Code Playgroud)protected override void QueueTask(Task task, bool wasPreviouslyQueued) { return TryExecuteTask(task); }其重载
Task.Factory.StartNew不接受aTaskScheduler使用来自的调度程序TaskFactory,在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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
样本输出:
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
Run Code Online (Sandbox Code Playgroud)
如您所见,有很多次重用等待线程来执行新任务.即使线程已获得锁定,也可能发生这种情况.讨厌的重新入侵.我感到非常震惊和担心:(