从Queue使用多个线程/任务进行消耗

时间:2013-06-20 18:46:24

标签: c# multithreading queue

我有一个生产者从资源中获取用户并将其放入ConcurrentQueue,然后我想要做的是使用多个消费者并处理所有用户并从其他资源获取他们的信息。

  public void Populate(IEnumerable<Users> users){
     _queue.Enqueue(users);
     // here single threaded
  }

  public void Process(){
     // here i want this to be processed by multiple consumers
     // say multiple threads so that I can finish processing them.
  }

我的问题是,我应该使用线程吗?任务?线程池?

我见过这个问题:C# equivalent for Java ExecutorService.newSingleThreadExecutor(), or: how to serialize mulithreaded access to a resource

1 个答案:

答案 0 :(得分:4)

由于您已经使用了排队机制,我建议您使用BlockingCollection代替ConcurrentQueue以及Parallel.Invoke()

BlockingCollection有一些重要的事情可以让它很好用。

  1. BlockingCollection允许消费线程使用foreach以线程安全且自然的方式从集合中获取项目。
  2. 消耗foreach循环在队列为空时自动阻止,并在项目可用时继续。
  3. BlockingCollection提供了一种易于使用的机制来发送数据结束信号。队列所有者只需调用queue.CompleteAdding(),当队列变为完全空时,从队列中获取项目的任何foreach循环将自动退出。
  4. 您可以使用Parallel.Invoke()启动多个线程,每个线程使用foreach来迭代队列。 (Parallel.Invoke()允许您为其提供一系列并行运行的任务,这使得使用起来非常简单。)

    最好用示例程序说明:

    using System;
    using System.Collections.Concurrent;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace Demo
    {
        class User
        {
            public string Name;
        }
    
        class Program
        {
            readonly BlockingCollection<User> _queue = new BlockingCollection<User>();
    
            void run()
            {
                var background = Task.Factory.StartNew(process); // Start the processing threads.
    
                // Make up 50 sample users.
                var users = Enumerable.Range(0, 50).Select(n => new User{Name = n.ToString()});
    
                foreach (var user in users) // Add some sample data.
                    _queue.Add(user);
    
                Console.WriteLine("Press <RETURN> to exit.");
                Console.ReadLine();
                _queue.CompleteAdding(); // Makes all the consuming foreach loops exit.
                background.Wait();
                Console.WriteLine("Exited.");
            }
    
            void process() // Process the input queue,
            {
                int taskCount = 4;  // Let's use 4 threads.
                var actions = Enumerable.Repeat<Action>(processQueue, taskCount);
                Parallel.Invoke(actions.ToArray());
            }
    
            void processQueue()
            {
                foreach (User user in _queue.GetConsumingEnumerable())
                    processUser(user);
            }
    
            void processUser(User user)
            {
                Console.WriteLine("Processing user " + user.Name);
                Thread.Sleep(200); // Simulate work.
            }
    
            static void Main()
            {
                new Program().run();
            }
        }
    }
    

    如果您不需要限制并发线程的数量并且很乐意让.Net为您决定(不是一个坏主意),那么您可以通过完全删除processQueue()来简化代码并将process()更改为:

    void process() // Process the input queue,
    {
        Parallel.ForEach(_queue.GetConsumingEnumerable(), processUser);
    }
    

    然而,这比它需要的锁定更多,所以你可能最好只使用原始方法(没有遇到这个问题),或者使用这里描述的解决方案:http://blogs.msdn.com/b/pfxteam/archive/2010/04/06/9990420.aspx < / p>