在多个任务之间共享BlockingCollection

时间:2012-06-29 05:04:49

标签: c# .net multithreading task-parallel-library

这是我的情景。我从外部数据源获取大量数据,我必须在两个地方本地写入。其中一个目标是写入速度非常慢但另一个目标是超快(但我不能依赖它来读取和写入慢速目标)。为此,我使用了Producer-Consumer模式(使用BlockingCollection)。

我现在遇到的问题是我必须将数据排入两个BlockingCollection并占用太多内存。我的代码看起来非常类似于下面的示例,但我真的想从单个队列中驱动两个Task。有谁知道这样做的正确方法是什么?下面代码中的任何效率低下?

class Program
{
    const int MaxNumberOfWorkItems = 15;
    static BlockingCollection<int> slowBC = new BlockingCollection<int>(MaxNumberOfWorkItems);
    static BlockingCollection<int> fastBC = new BlockingCollection<int>(MaxNumberOfWorkItems);

    static void Main(string[] args)
    {
        Task slowTask = Task.Factory.StartNew(() =>
        {
            foreach (var item in slowBC.GetConsumingEnumerable())
            {
                Console.WriteLine("SLOW -> " + item);
                Thread.Sleep(25);
            }
        });

        Task fastTask = Task.Factory.StartNew(() =>
        {
            foreach (var item in fastBC.GetConsumingEnumerable())
            {
                Console.WriteLine("FAST -> " + item);
            }
        });

        // Population two BlockingCollections with the same data. How can I have a single collection?
        for (int i = 0; i < 100; i++)
        {
            while (slowBC.TryAdd(i) == false)
            {
                Console.WriteLine("Wait for slowBC...");
            }

            while (fastBC.TryAdd(i) == false)
            {
                Console.WriteLine("Wait for 2...");
            }
        }

        slowBC.CompleteAdding();
        fastBC.CompleteAdding();

        Task.WaitAll(slowTask, fastTask);

        Console.ReadLine();
    }
}

1 个答案:

答案 0 :(得分:0)

  1. 使用生产者 - 消费者队列来传输单个整数是非常低效的。你是块的rx,所以为什么不把队列键入'* chunk'并发送整个块,立即在同一个ref创建/ depooling一个新的块。 rx变通。下一批数据?这就是P-C队列通常用于非平凡数据量的方式 - 排队引用/指针,而不是实际数据。线程有共享内存空间(一些开发人员似乎认为只会导致问题),所以使用它 - 队列指针/引用并安全地将MB数据作为一个指针传输。只要你,在下一行代码中,总是在排除旧代码后创建/删除一个新代码,生产者和消费者线程永远不能在同一个块上运行。

    排队*块是大块的效率提高10倍。

  2. 将*数据块发送到快速链接,然后将它们“转发”到那里的慢速链接。

  3. 如果慢速链接不阻塞系统并导致最终的OOM错误,则可能需要进行流量控制。我通常做的是修复总缓冲区大小的“总体”配额,并在启动时创建一个块池(池是另一个BlockingCollection,在启动时填充* new(块))。生成器线程将块取出,用数据填充它们,将它们排队到FAST线程。 FAST线程处理收到的块,然后将*块排队到SLOW线程。 SLOW线程处理相同的数据,然后重新填充'used'块以供生产者线程重用。这形成了一个流控制系统 - 如果SLOW thred太慢,生产者最终会尝试从空池中清空一个*块,然后阻塞它,直到SLOW线程重新使用一些使用过的*块,从而表示生成器线程运行再次。您可能需要在慢速线程中使用某些策略来超时其操作并尽早转储其*块,因此丢弃数据 - 您必须根据您的总体要求决定策略 - 显然不可能将数据连续排队到快速并且在没有内存溢出的情况下永远减缓消费者,除非缓慢的消费者转储一些数据。

  4. 编辑 - 哦,是的,使用池可以消除使用过的块上的GC,从而进一步提高性能。

    一个整体流策略是不在慢速线程中转储任何数据。随着持续的高数据流,*块将最终进入快速和慢速线程之间的队列,生产者线程将确实阻塞空池。然后,网络连接将应用自己的flow-contol来阻止网络对等体通过TCP发送更多数据。这会将流量控制从慢速线程扩展到对等端。