什么决定了TaskFactory衍生作业的线程数?

Ang*_*ker 9 .net c# multithreading task-parallel-library

我有以下代码:

var factory = new TaskFactory();
for (int i = 0; i < 100; i++)
{
    var i1 = i;
    factory.StartNew(() => foo(i1));
}

static void foo(int i)
{
    Thread.Sleep(1000);
    Console.WriteLine($"foo{i} - on thread {Thread.CurrentThread.ManagedThreadId}");
}            
Run Code Online (Sandbox Code Playgroud)

我可以看到它一次只做4个线程(基于观察).我的问题:

  1. 什么决定了一次使用的线程数?
  2. 我该如何找回这个号码?
  3. 我该如何更改此号码?

PS我的盒子有4个核心.

PPS我需要具有特定数量的任务(并且不再需要)由TPL同时处理并最终得到以下代码:

private static int count = 0;   // keep track of how many concurrent tasks are running

private static void SemaphoreImplementation()
{
    var s = new Semaphore(20, 20);  // allow 20 tasks at a time

    for (int i = 0; i < 1000; i++)
    {
        var i1 = i;

        Task.Factory.StartNew(() =>
        {
            try
            {                        
                s.WaitOne();
                Interlocked.Increment(ref count);

                foo(i1);
            }
            finally
            {
                s.Release();
                Interlocked.Decrement(ref count);
            }
        }, TaskCreationOptions.LongRunning);
    }
}

static void foo(int i)
{
    Thread.Sleep(100);
    Console.WriteLine($"foo{i:00} - on thread " + 
            $"{Thread.CurrentThread.ManagedThreadId:00}. Executing concurently: {count}");
}
Run Code Online (Sandbox Code Playgroud)

小智 15

当你Task在.NET 中使用时,你告诉TPL安排一项工作(通过TaskScheduler)来执行ThreadPool.请注意,工作将尽早安排,但调度程序认为合适.这意味着TaskScheduler将决定将使用多少个线程来运行n多个任务以及在哪个线程上执行哪个任务.

TPL经过精心调整,并在执行任务时继续调整算法.因此,在大多数情况下,它会尽量减少争用.这意味着如果您运行100个任务并且只有4个核心(您可以使用Environment.ProcessorCount它),那么在任何给定时间执行4个以上的线程都没有意义,否则它将需要进行更多的上下文切换.现在,有时您希望显式覆盖此行为.让我们说在你需要等待某种IO完成的情况下,这是一个完全不同的故事.

总之,请相信TPL.但是如果你坚持每个任务产生一个线程(并不总是一个好主意!),你可以使用:

Task.Factory.StartNew(
    () => /* your piece of work */, 
    TaskCreationOptions.LongRunning);
Run Code Online (Sandbox Code Playgroud)

这告诉DefaultTaskscheduler为该工作明确生成一个新线程.

您也可以使用自己的Scheduler并将其传递给TaskFactory.你可以找到一大堆Schedulers HERE.

注意另一种替代方法是使用PLINQ默认情况下分析您的查询并决定是否并行化它会产生任何好处,再次在阻塞IO的情况下,您确定启动多个线程将导致更好的执行,您可以强制执行WithExecutionMode(ParallelExecutionMode.ForceParallelism)然后使用你的并行性可以使用WithDegreeOfParallelism来提供有关使用多少线程的提示,但是请记住,不能保证你会得到那么多线程,如MSDN所说:

设置要在查询中使用的并行度.并行度是将用于处理查询的并发执行任务的最大数量.

最后,我强烈建议您阅读THIS一系列关于Threading和的文章TPL.

  • TPL不知道工作量是做什么的.它*不可能*适合IO.TPL"将做正确的事情"的想法在整个网络上重复,但是错误的.这个问题就是一个很好的例子:4个线程是吞吐量限制的选择. (2认同)
  • @usr 正是我的想法。TPL 不可能知道我的工作流程。我最终用“TaskCreationOptions.LongRunning”加上信号量来实现它。 (2认同)

usr*_*usr 5

如果将任务数量增加到例如 1000000 个,您将看到随着时间的推移产生更多的线程。TPL 倾向于每 500 毫秒注入一次。

TPL 线程池不理解 IO 密集型工作负载(睡眠是 IO)。在这些情况下,依靠 TPL 来选择正确的并行度并不是一个好主意。TPL 完全无能为力,并且根据对吞吐量的模糊猜测来注入更多线程。也是为了避免死锁。

在这里,TPL 策略显然没有用,因为添加的线程越多,获得的吞吐量就越大。在这种人为的情况下,每个线程每秒可以处理一个项目。TPL 对此一无所知。将线程数限制为核心数是没有意义的。

什么决定了一次使用的线程数?

几乎没有记录 TPL 启发法。他们经常出错。特别是在这种情况下,随着时间的推移,它们将产生无限数量的线程。使用任务管理器亲自查看。让它运行一个小时,您将拥有 1000 个线程。

我怎样才能找回这个号码?我怎样才能更改这个号码?

您可以检索其中一些数字,但这不是正确的方法。如果您需要有保证的 DOP,您可以使用AsParallel().WithDegreeOfParallelism(...)或自定义任务调度程序。您也可以手动启动LongRunning任务。不要弄乱进程全局设置。