使用ConcurrentQueue时,尝试在并行循环时出列

时间:2010-06-08 13:43:12

标签: concurrency c#-4.0 plinq pfx

我在我的.NET 4应用程序中使用并行数据结构,并且在我处理它时会添加ConcurrentQueue

我想做类似的事情:

personqueue.AsParallel().WithDegreeOfParallelism(20).ForAll(i => ... );

当我进行数据库调用以保存数据时,所以我限制了并发线程的数量。

但是,我希望ForAll不会出列,而我只关心做什么

ForAll(i => {
    personqueue.personqueue.TryDequeue(...);
    ...
});

因为无法保证我正在弹出正确的。

那么,我怎样才能以并行的方式遍历集合并出列队列。

或者,并行使用PLINQ进行此处理会更好吗?

2 个答案:

答案 0 :(得分:4)

嗯,我不是100%确定你在这里尝试存档的内容。您是否只想将所有物品出列,直到什么都没有留下?或者只是一次性出列很多物品?

第一个可能出乎意料的行为始于这个陈述:

 theQueue.AsParallel()

对于ConcurrentQueue,您将获得一个“快照” - 枚举器。因此,当您迭代并发堆栈时,您只会遍历快照,而不是“实时”队列。

总的来说,我认为在迭代过程中迭代你正在改变的东西并不是一个好主意。

所以另一个解决方案看起来像这样:

        // this way it's more clear, that we only deque for theQueue.Count items
        // However after this, the queue is probably not empty
        // or maybe the queue is also empty earlier   
        Parallel.For(0, theQueue.Count,
                     new ParallelOptions() {MaxDegreeOfParallelism = 20},
                     () => { 
                         theQueue.TryDequeue(); //and stuff
                     });

这可以避免在迭代时操纵某些东西。但是,在该语句之后,队列仍然可以包含在for循环期间添加的数据。

要及时清空队列,您可能需要多做一些工作。这是一个非常难看的解决方案。虽然队列仍有项目,但创建新任务。每个任务开始都会从队列中出队,只要它可以。最后,我们等待所有任务结束。为了限制并行性,我们永远不会创建超过20个任务。

        // Probably a kitty died because of this ugly code ;)
        // However, this code tries to get the queue empty in a very aggressive way
        Action consumeFromQueue = () =>
                                      {
                                          while (tt.TryDequeue())
                                          {
                                              ; // do your stuff
                                          }
                                      };
        var allRunningTasks = new Task[MaxParallism];
        for(int i=0;i<MaxParallism && tt.Count>0;i++)
        {
            allRunningTasks[i] = Task.Factory.StartNew(consumeFromQueue);  
        }
        Task.WaitAll(allRunningTasks);

答案 1 :(得分:0)

如果你的目标是整个真实网站的高端,你不必立即进行数据库更新,你会更好地选择非常保守的解决方案而不是额外的图层库。

制作固定大小的数组(猜测大小 - 比如1000项或N秒的请求)和互锁索引,以便请求只将数据放入插槽并返回。当一个块被填满(继续检查计数)时,再创建一个并生成异步委托来处理并向SQL发送刚填充的块。根据数据的结构,委托可以将所有数据打包成逗号分隔的数组,甚至可能是一个简单的XML(当然要测试那个数据的性能)并将它们发送到SQL sproc,这应该给它最好处理它们的记录记录 - 从不持有大锁。如果变重,您可以将块分成几个较小的块。关键是你最小化了对SQL的请求数量,始终保持一定程度的分离,甚至不必为线程池付出代价 - 你可能根本不需要使用更多的2个异步线程

摆弄Parallel-s会快得多。