消耗多个任务/消费者的阻塞集合

时间:2013-06-21 20:31:58

标签: c# multithreading task-parallel-library

我有以下代码,我从源填充用户,为例子如下所示。我想要做的是使用多个消费者的BlockingCollection。

低于正确的方法吗?还有什么是最好的线程数?好吧这取决于硬件,内存等。或者我怎样才能以更好的方式做到这一点?

下面的实现也会确保我将处理集合中的所有内容,直到它为空?

    class Program
    {
        public static readonly BlockingCollection<User> users = new BlockingCollection<User>();

        static void Main(string[] args)
        {
            for (int i = 0; i < 100000; i++)
            {
                var u = new User {Id = i, Name = "user " + i};
                users.Add(u);
            }

            Run(); 
        }

        static void Run()
        {
            for (int i = 0; i < 100; i++)
            {
                Task.Factory.StartNew(Process, TaskCreationOptions.LongRunning);
            }
        }

        static void Process()
        {
            foreach (var user in users.GetConsumingEnumerable())
            {
                Console.WriteLine(user.Id);
            }
        }
    }

    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

1 个答案:

答案 0 :(得分:5)

一些小事

  1. 你从未打电话给CompleteAdding,没有这样做你的消费foreach循环永远不会完成并永远挂起。通过在初始users.CompleteAdding()循环后执行for来解决此问题。
  2. 你永远不会等待工作完成,Run()将启动你的100个线程(除非你的真实过程涉及大量等待无争议的资源,否则可能会过多)。由于任务不是前台线程,因此当您Main退出时,它们不会保持您的程序处于打开状态。您需要CountdownEvent来跟踪所有内容的完成情况。
  3. 在制作人完成所有工作之前,你不会启动你的消费者,你应该将生产者分拆到一个单独的线程或首先启动消费者,这样他们就可以在你填充生产者的时候工作了主线。
  4. 这是带有修复程序的代码的更新版本

    class Program
    {
        private const int MaxThreads = 100; //way to high for this example.
        private static readonly CountdownEvent cde = new CountdownEvent(MaxThreads);
        public static readonly BlockingCollection<User> users = new BlockingCollection<User>();
    
        static void Main(string[] args)
        {
            Run(); 
    
            for (int i = 0; i < 100000; i++)
            {
                var u = new User {Id = i, Name = "user " + i};
                users.Add(u);
            }
            users.CompleteAdding();
            cde.Wait();
        }
    
        static void Run()
        {
            for (int i = 0; i < MaxThreads; i++)
            {
                Task.Factory.StartNew(Process, TaskCreationOptions.LongRunning);
            }
        }
    
        static void Process()
        {
            foreach (var user in users.GetConsumingEnumerable())
            {
                Console.WriteLine(user.Id);
            }
            cde.Signal();
        }
    }
    
    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    

    对于我之前说过的“最佳线程数”,这实际上取决于你在等什么。

    如果您正在处理的是CPU绑定,则最佳线程数可能为Enviorment.ProcessorCount

    如果您正在等待外部资源,但新请求不会影响旧请求(例如,询问20个不同的服务器以获取信息,服务器上的服务器n上的负载不会影响服务器上的负载n+1)在这种情况下,我会让Parallel.ForEach为您选择线程数。

    如果您正在等待争用的资源(例如读/写硬盘),您将不想使用很多线程(甚至可能只使用一个)。我刚刚发布了a answer in another question关于这一点,当从硬盘读入时,你应该一次只使用一个线程,这样硬盘驱动器就不会一遍遍地试图完成所有的读取。