如何在ConcurrentQueue或ConcurrentStack中使用IObservable / IObserver

时间:2010-06-13 00:47:33

标签: c#-4.0 plinq system.reactive concurrent-programming

我意识到当我尝试使用多个线程处理并发队列中的项目而多个线程可以将项目放入其中时,理想的解决方案是将Reactive Extensions与并发数据结构一起使用。

我原来的问题是:

While using ConcurrentQueue, trying to dequeue while looping through in parallel

所以我很好奇是否有任何方法可以让LINQ(或PLINQ)查询在项目投入时不断出列。

我试图以一种方式使用这种方式,我可以让n个生成器进入队列并处理有限数量的线程,所以我不会重载数据库。

如果我可以使用Rx框架,那么我希望我可以启动它,如果在100ms内放置了100个项目,那么作为PLINQ查询一部分的20个线程将只处理整个队列。

我正在努力合作三种技术:

  1. Rx Framework(Reactive LINQ)
  2. PLING
  3. System.Collections.Concurrent 结构

2 个答案:

答案 0 :(得分:6)

Drew是对的,我认为ConcurrentQueue虽然听起来很完美,但实际上是BlockingCollection使用的底层数据结构。似乎也很重要。 查看本书第7章* http://www.amazon.co.uk/Parallel-Programming-Microsoft-NET-Decomposition/dp/0735651590/ref=sr_1_1?ie=UTF8&qid=1294319704&sr=8-1 它将解释如何使用BlockingCollection并让多个生产者和多个消费者各自取消“队列”。您将需要查看“GetConsumingEnumerable()”方法,并可能只调用.ToObservable()。

*本书的其余部分相当平均。

编辑:

这是一个示例程序,我认为你做了什么?

class Program
{
    private static ManualResetEvent _mre = new ManualResetEvent(false);
    static void Main(string[] args)
    {
        var theQueue = new BlockingCollection<string>();
        theQueue.GetConsumingEnumerable()
            .ToObservable(Scheduler.TaskPool)
            .Subscribe(x => ProcessNewValue(x, "Consumer 1", 10000000));

        theQueue.GetConsumingEnumerable()
            .ToObservable(Scheduler.TaskPool)
            .Subscribe(x => ProcessNewValue(x, "Consumer 2", 50000000));

        theQueue.GetConsumingEnumerable()
            .ToObservable(Scheduler.TaskPool)
            .Subscribe(x => ProcessNewValue(x, "Consumer 3", 30000000));


        LoadQueue(theQueue, "Producer A");
        LoadQueue(theQueue, "Producer B");
        LoadQueue(theQueue, "Producer C");

        _mre.Set();

        Console.WriteLine("Processing now....");

        Console.ReadLine();
    }

    private static void ProcessNewValue(string value, string consumerName, int delay)
    {
        Thread.SpinWait(delay);
        Console.WriteLine("{1} consuming {0}", value, consumerName);
    }

    private static void LoadQueue(BlockingCollection<string> target, string prefix)
    {
        var thread = new Thread(() =>
                                    {
                                        _mre.WaitOne();
                                        for (int i = 0; i < 100; i++)
                                        {
                                            target.Add(string.Format("{0} {1}", prefix, i));
                                        }
                                    });
        thread.Start();
    }
}

答案 1 :(得分:3)

我不知道如何使用Rx实现这一目标,但我建议您只使用BlockingCollection<T>producer-consumer pattern。您的主线程将项添加到集合中,默认情况下使用下面的ConcurrentQueue<T>。然后你有一个单独的Task,你可以在Parallel::ForEach之上使用BlockingCollection<T>来处理它,以便同时处理系统中同样多的项目。现在,您可能还需要研究使用ParallelExtensions库的GetConsumingPartitioner方法,以便最有效,因为默认分区程序会产生比此情况下更多的开销。您可以从this blog post了解更多相关信息。

主线程结束后,您可以拨打BlockingCollection<T>上的CompleteAddingTask上的{{1}},等待所有消费者完成全部处理集合中的项目。