当消费者不堪重负时,如何让快速生产者暂停?

时间:2016-08-13 17:13:10

标签: c# .net task-parallel-library tpl-dataflow dataflow

我的应用中使用TPL Dataflow实现了生产者/消费者模式。我有大数据流网格,其中有大约40个块。网格中有两个主要功能部分:生产者部分和消费者部分。生产者应该继续为消费者提供大量工作,而消费者有时会缓慢地处理传入的工作。当消费者忙于一些指定数量的工作项时,我想暂停生产者。否则,该应用程序会占用大量内存/ CPU并且行为不可持续。

我制作了演示该问题的演示应用程序:

mesh

using System;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

namespace DataflowTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var options = new ExecutionDataflowBlockOptions
            {
                MaxDegreeOfParallelism = 4,
                EnsureOrdered = false
            };

            var boundedOptions = new ExecutionDataflowBlockOptions
            {
                MaxDegreeOfParallelism = 4,
                EnsureOrdered = false,
                BoundedCapacity = 5
            };

            var bufferBlock = new BufferBlock<int>(boundedOptions);
            var producerBlock = new TransformBlock<int, int>(x => x + 1, options);
            var broadcastBlock = new BroadcastBlock<int>(x => x, options);

            var consumerBlock = new ActionBlock<int>(async x =>
            {
                var delay = 1000;
                if (x > 10) delay = 5000;

                await Task.Delay(delay);

                Console.WriteLine(x);
            }, boundedOptions);

            producerBlock.LinkTo(bufferBlock);
            bufferBlock.LinkTo(broadcastBlock);
            broadcastBlock.LinkTo(producerBlock);
            broadcastBlock.LinkTo(consumerBlock);

            bufferBlock.Post(1);

            consumerBlock.Completion.Wait();            
        }        
    }
}

该应用打印出类似这样的内容:

2
1
3
4
5
69055
69053
69054
69057
438028
438040
142303
438079

这意味着制作人不断旋转并将消息推送给消费者。我希望它暂停并等到消费者完成当前部分工作,然后生产者应继续为消费者提供消息。

我的问题引用类似于其他question,但未得到正确回答。我尝试了这个解决方案,它在这里不起作用,允许生产者用消息充斥消费者。同时设置BoundedCapacity也不起作用。

我猜到目前为止唯一的解决方案就是创建自己的块来监视目标块队列,并根据目标块的队列进行操作。但我希望这个问题有点矫枉过正。

1 个答案:

答案 0 :(得分:5)

如果您需要保持生产者→缓冲区→广播周期不变,那么您需要将广播块替换为仍然广播其收到的消息的其他块,但是当其中一个目标已满时等待。

只要您在创建该块时知道该块的目标,就可以使用ActionBlock(从another answer of mine复制的代码)构建它:

public static ITargetBlock<T> CreateGuaranteedBroadcastBlock<T>(
    DataflowBlockOptions options, params ITargetBlock<T>[] targets)
{
    var block = new ActionBlock<T>(
        async item =>
        {
            foreach (var target in targets)
            {
                await target.SendAsync(item);
            }
        }, new ExecutionDataflowBlockOptions
        {
            BoundedCapacity = options.BoundedCapacity,
            CancellationToken = options.CancellationToken
        });

    block.Completion.ContinueWith(task =>
    {
        foreach (var target in targets)
        {
            if (task.Exception != null)
                target.Fault(task.Exception);
            else
                target.Complete();
        }
    });

    return block;
}

使用此功能,您可以声明广播块:

var broadcastBlock = CreateGuaranteedBroadcastBlock(
    boundedOptions, producerBlock, consumerBlock);

(您还需要删除与LinkTo相关联的broadcastBlock行。)

原始代码的一个问题是这个问题无法解决,但是在TPL数据流中这是一个很常见的问题。