Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/313.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 第三方物流数据流-非常快的生产者,没有那么快的消费者出内存例外_C#_.net_Producer Consumer_Tpl Dataflow - Fatal编程技术网

C# 第三方物流数据流-非常快的生产者,没有那么快的消费者出内存例外

C# 第三方物流数据流-非常快的生产者,没有那么快的消费者出内存例外,c#,.net,producer-consumer,tpl-dataflow,C#,.net,Producer Consumer,Tpl Dataflow,在将TPL数据流移植到生产代码之前,我正在试验它。 生产代码是一个经典的生产者/消费者系统——生产者产生消息(与金融领域相关),消费者处理这些消息 我感兴趣的是,如果在某一点上生产商的生产速度远远快于消费者的处理速度(系统是否会崩溃,或者会发生什么),环境会保持多稳定&更重要的是,在这种情况下该怎么办 因此,为了尝试使用类似的简单应用程序,我提出了以下建议 var bufferBlock = new BufferBlock<Item>(); var executiondataflo

在将TPL数据流移植到生产代码之前,我正在试验它。 生产代码是一个经典的生产者/消费者系统——生产者产生消息(与金融领域相关),消费者处理这些消息

我感兴趣的是,如果在某一点上生产商的生产速度远远快于消费者的处理速度(系统是否会崩溃,或者会发生什么),环境会保持多稳定&更重要的是,在这种情况下该怎么办

因此,为了尝试使用类似的简单应用程序,我提出了以下建议

var bufferBlock = new BufferBlock<Item>();

var executiondataflowBlockOptions = new ExecutionDataflowBlockOptions
{
    MaxDegreeOfParallelism = Environment.ProcessorCount
                        ,
    BoundedCapacity = 100000
};

var dataFlowLinkOptions = new DataflowLinkOptions
{
    PropagateCompletion = true
};

var actionBlock1 = new ActionBlock<Item>(t => ProcessItem(t),
    executiondataflowBlockOptions);

bufferBlock.LinkTo(actionBlock1, dataFlowLinkOptions);
for (int i = 0; i < int.MaxValue; i++)
{
    bufferBlock.SendAsync(GenerateItem());
}

bufferBlock.Complete();
Console.ReadLine();
GenerateItem
只需发布新闻
Item

static Item GenerateItem()
{
   return new Item(Guid.NewGuid().ToString());
}
现在,为了模仿速度不太快的消费者,我让
ProcessItem
保持
100ms

static async Task ProcessItem(Item item)
{
    await Task.Delay(TimeSpan.FromMilliseconds(100));
    Console.WriteLine($"Processing #{item.ItemId} item.");
}
执行此操作将在20秒左右的时间内导致OOM异常

然后我继续添加了更多的消费者(最多10个ActionBlock),这赢得了更多的时间,但最终导致了相同的OOM异常

我还注意到GC承受着巨大的压力(VS 2015诊断工具显示GC几乎一直在运行),所以我为
引入了对象池(非常简单的一个,本质上是
ConcurrentBag
存储项),但我仍然遇到了同样的问题(抛出了OOM异常)

来详细说明内存中的内容以及内存不足的原因

  • 最大大小的对象的类型为
    SingleProducerSingleConsumerQueue+段
    &
    ConcurrentQueue+段
  • 我看到
    BufferBlock
    的InputBuffer中充满了
    s(Count=14562296)
  • 由于我为
    ActionBlock
    (s)设置了
    BoundedCapacity
    ,它们的输入缓冲区也接近配置的数字(InputCount=99996)
为了确保较慢的生产者能够让消费者跟上,我让生产者在迭代之间睡觉:

for (int i = 0; i < int.MaxValue; i++)
{
    Thread.Sleep(TimeSpan.FromMilliseconds(50));
    bufferBlock.SendAsync(GenerateItem());
}
for(int i=0;i
它工作得很好——没有抛出异常,内存使用率一直很低,我再也看不到任何GC压力

所以我有几个问题

  • 当我试图用TPL数据流构建块重现非常快的生产者/缓慢的消费者场景时,我是否做了一些固有的错误
  • 有没有办法使这项工作,而不是失败的例外
  • 关于如何在TPL数据流上下文中处理此类场景(非常快的生产者/缓慢的消费者)的最佳实践的任何评论/链接
  • 我对这个问题的理解是——由于消费者无法跟上,
    BufferBlock
    的内部缓冲区很快就充满了消息,并延迟消息,直到一些消费者回来请求下一条消息,结果应用程序内存不足(由于
    BufferBlock
    的内部缓冲区已满)-您同意吗
  • 我使用的是
    Microsoft.Tpl.Dataflow
    package-version4.5.24。
    .NET4.5(C#6)。进程是32位的。

    您已经很好地识别了问题:缓冲块
    正在填充其输入缓冲区,直到到达OOM


    要解决此问题,您还应在缓冲区块中添加一个
    BoundedCapacity
    选项。这将为您自动限制生产者(不需要生产者中的
    线程。Sleep

    以下代码可能存在严重问题:

    for (int i = 0; i < int.MaxValue; i++)
    {
        bufferBlock.SendAsync(GenerateItem()); // Don't do this!
    }
    
    这样,生产者将被异步阻止,直到区块内有足够的空间容纳发送的项目。这正是您希望的。否则,如果您不想阻止生产者,请不要使用asynchronous
    SendAsync
    方法,而是使用synchronous方法:

    for (int i = 0; i < int.MaxValue; i++)
    {
        var item = GenerateItem();
        while (true)
        {
            bool accepted = bufferBlock.Post(item); // Synchronous call
            if (accepted) break; // Break the inner loop
            if (bufferBlock.Completion.IsCompleted) return; // Break both loops
    
            // Here do other things for a while, before retrying to post the item
        }
    }
    
    幻数
    1L
    是TPL数据流中内部声明的值,表示:

    用于只发送一条消息或确切消息ID不重要的代码的已知消息ID


    感谢您的确认!请您对BufferBlock的
    BoundedCapacity
    行为发表意见。特别是当内部缓冲区已满时,传入消息会发生什么情况?它们会被丢弃吗?(目前似乎是这样)。您的书中可能有更详细的内容吗?斯蒂芬?@迈克尔:消息永远不会被删除。如果
    将消息发布到完整块,它将返回
    false
    (表示消息未被接受)。如果
    等待SendAsync
    将消息发送到完整块,它将(异步)返回等待有空位,然后发布消息。看。谢谢@StephenCleary!
    for (int i = 0; i < int.MaxValue; i++)
    {
        await bufferBlock.SendAsync(GenerateItem()); // It's OK now
    }
    
    for (int i = 0; i < int.MaxValue; i++)
    {
        var item = GenerateItem();
        while (true)
        {
            bool accepted = bufferBlock.Post(item); // Synchronous call
            if (accepted) break; // Break the inner loop
            if (bufferBlock.Completion.IsCompleted) return; // Break both loops
    
            // Here do other things for a while, before retrying to post the item
        }
    }
    
    for (int i = 0; i < int.MaxValue; i++)
    {
        var item = GenerateItem();
        while (true)
        {
            var offerResult = ((ITargetBlock<Item>)bufferBlock).OfferMessage(
                new DataflowMessageHeader(1L), item, null, false);
            if (offerResult == DataflowMessageStatus.Accepted) break;
            if (offerResult == DataflowMessageStatus.DecliningPermanently) return;
    
            // Here do other things for a while, before retrying to offer the item
        }
    }