C# 使用任务并行库时线程数的增长

C# 使用任务并行库时线程数的增长,c#,multithreading,parallel-processing,task-parallel-library,producer-consumer,C#,Multithreading,Parallel Processing,Task Parallel Library,Producer Consumer,我使用的是C#TPL,我的生产者/消费者代码有问题。。。出于某种原因,TPL不重用线程,而是不断地创建新线程而不停止 我举了一个简单的例子来说明这种行为: class Program { static BlockingCollection<int> m_Buffer = new BlockingCollection<int>(1); static CancellationTokenSource m_Cts = new CancellationTokenSo

我使用的是C#TPL,我的生产者/消费者代码有问题。。。出于某种原因,TPL不重用线程,而是不断地创建新线程而不停止

我举了一个简单的例子来说明这种行为:

class Program
{
    static BlockingCollection<int> m_Buffer = new BlockingCollection<int>(1);
    static CancellationTokenSource m_Cts = new CancellationTokenSource();

    static void Producer()
    {
        try
        {
            while (!m_Cts.IsCancellationRequested)
            {
                Console.WriteLine("Enqueuing job");
                m_Buffer.Add(0);
                Thread.Sleep(1000);
            }
        }
        finally
        {
            m_Buffer.CompleteAdding();
        }
    }

    static void Consumer()
    {
        Parallel.ForEach(m_Buffer.GetConsumingEnumerable(), Run);
    }

    static void Run(int i)
    {
        Console.WriteLine
            ("Job Processed\tThread: {0}\tProcess Thread Count: {1}",
              Thread.CurrentThread.ManagedThreadId, 
              Process.GetCurrentProcess().Threads.Count);
    }

    static void Main(string[] args)
    {
        Task producer = new Task(Producer);
        Task consumer = new Task(Consumer);
        producer.Start();
        consumer.Start();

        Console.ReadKey();
        m_Cts.Cancel();

        Task.WaitAll(producer, consumer);
    }
}
类程序
{
静态BlockingCollection m_Buffer=新BlockingCollection(1);
静态CancellationTokenSource m_Cts=新的CancellationTokenSource();
静态空隙发生器()
{
尝试
{
而(!m_Cts.iscancellationrequest)
{
控制台写入线(“排队作业”);
m_Buffer.Add(0);
睡眠(1000);
}
}
最后
{
m_Buffer.CompleteAdding();
}
}
静态无效使用者()
{
Parallel.ForEach(m_Buffer.getconsumineGenumerable(),Run);
}
静态无效运行(int i)
{
控制台写入线
(“已处理作业\t线程:{0}\t进程线程计数:{1}),
Thread.CurrentThread.ManagedThreadId,
Process.GetCurrentProcess().Threads.Count);
}
静态void Main(字符串[]参数)
{
任务生产者=新任务(生产者);
任务使用者=新任务(使用者);
producer.Start();
consumer.Start();
Console.ReadKey();
m_Cts.Cancel();
Task.WaitAll(生产者、消费者);
}
}
此代码创建两个任务,生产者和消费者。Products每秒添加1个工作项,而Consumer只打印出一个包含信息的字符串。我假设在这种情况下,1个使用者线程就足够了,因为任务的处理速度比添加到队列的速度快得多,但实际发生的情况是,进程中的线程数每秒增加1。。。就好像TPL正在为每个项目创建新线程一样

在试图了解发生了什么之后,我还注意到了另一件事:即使BlockingCollection大小为1,但在一段时间后,消费者开始被突然调用,例如,它是这样开始的:

排队作业

作业处理线程:4进程线程计数:9

排队作业

作业处理线程:6进程线程计数:9

排队作业

作业处理线程:5进程线程计数:10

排队作业

作业处理线程:4进程线程计数:10

排队作业

作业处理线程:6进程线程计数:11

这就是它在不到一分钟后处理项目的方式:

排队作业

作业已处理线程:25进程线程计数:52

排队作业

排队作业

作业处理线程:5进程线程计数:54

作业处理线程:5进程线程计数:54

由于线程在完成Parallel.ForEach循环后被释放(我在本例中没有显示,但它是在实际项目中),所以我假设它与ForEach有关。。。我发现了这篇文章,我认为我的问题是由这个默认分区器引起的,所以我从TPL示例中获取了自定义分区器,它一个接一个地为消费者线程项提供信息,尽管它修复了执行顺序(消除了延迟)

排队作业

作业处理线程:71进程线程计数:140

排队作业

作业处理线程:12进程线程计数:141

排队作业

作业处理线程:72进程线程计数:142

排队作业

作业处理线程:38进程线程计数:143

排队作业

作业处理线程:73进程线程计数:143

排队作业

作业处理线程:21进程线程计数:144

排队作业

作业已处理线程:74进程线程计数:145

…它并没有阻止线程的增长

我知道ParallelOptions.MaxDegreeOfParallelism,但我仍然想了解TPL发生了什么,以及为什么它会毫无理由地创建数百个线程


在我的项目中,我需要运行数小时,从数据库中读取新数据,将其放入BlockingCollections,并由其他代码处理数据,大约每5秒有一个新项,处理它需要几毫秒到几乎一分钟,运行大约10分钟后,线程数超过1000个线程

有两个因素共同导致此行为:

class Program
{
    static BlockingCollection<int> m_Buffer = new BlockingCollection<int>(1);
    static CancellationTokenSource m_Cts = new CancellationTokenSource();

    static void Producer()
    {
        try
        {
            while (!m_Cts.IsCancellationRequested)
            {
                Console.WriteLine("Enqueuing job");
                m_Buffer.Add(0);
                Thread.Sleep(1000);
            }
        }
        finally
        {
            m_Buffer.CompleteAdding();
        }
    }

    static void Consumer()
    {
        Parallel.ForEach(m_Buffer.GetConsumingEnumerable(), Run);
    }

    static void Run(int i)
    {
        Console.WriteLine
            ("Job Processed\tThread: {0}\tProcess Thread Count: {1}",
              Thread.CurrentThread.ManagedThreadId, 
              Process.GetCurrentProcess().Threads.Count);
    }

    static void Main(string[] args)
    {
        Task producer = new Task(Producer);
        Task consumer = new Task(Consumer);
        producer.Start();
        consumer.Start();

        Console.ReadKey();
        m_Cts.Cancel();

        Task.WaitAll(producer, consumer);
    }
}
  • ThreadPool
    尝试根据您的情况使用最佳线程数。但是,如果池中的一个线程阻塞,池就会认为该线程没有做任何有用的工作,因此它倾向于在这之后不久创建另一个线程。这意味着,如果有大量的阻塞,
    ThreadPool
    在猜测最佳线程数方面非常糟糕,它倾向于创建新线程,直到达到极限

  • Parallel.ForEach()
    Parallel.ForEach()
    主要用于有界集合,而不是数据流

  • 当您将这两件事与
    getConsumineumerable()
    结合起来时,您得到的是
    Parallel.ForEach()
    创建的线程几乎总是被阻塞的。
    ThreadPool
    看到了这一点,为了保持CPU的利用率,创建了越来越多的线程

    正确的解决方案是设置
    MaxDegreeOfParallelism
    。如果您的计算受CPU限制,则最有可能是最佳值。如果它们受IO约束,则必须通过实验找出最佳值

    如果可以使用.NET4.5,另一个选项是使用TPL数据流。这个库是专门用来处理数据流的,就像你所拥有的,所以它没有问题