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

What determines the number of threads for a TaskFactory spawned jobs?

我有以下代码:

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}");
}            

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

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

P.S。我的盒子是4芯的。

P.P.S。我需要有特定数量的任务(不能更多)由 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}");
}

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

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

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

Task.Factory.StartNew(
    () => /* your piece of work */, 
    TaskCreationOptions.LongRunning);

这告诉 DefaultTaskscheduler 显式为该工作生成一个新线程。

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

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

Sets the degree of parallelism to use in a query. Degree of parallelism is the maximum number of concurrently executing tasks that will be used to process the query.

最后,我高度推荐阅读THIS关于ThreadingTPL的精彩系列文章。

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

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

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

What determines the number of threads used at a time?

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

How can I retrieve this number? How can I change this number?

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