管道会保留缓冲区,直到处理完成

问题描述

我正在研究使用管道处理来自网络的二进制消息的可能性。 我将要处理的二进制消息带有一个有效载荷,因此希望将有效载荷保持其二进制形式。

这个想法是读出整个消息并创建一条消息及其有效载荷的一部分,一旦消息被完全读取,它将被传递到通道链进行处理,处理将不会立即进行,可能会花费一些时间或稍后执行,目标是不要让管道读取器等待,直到处理完成,然后,一旦消息处理完成,我就需要将处理后的缓冲区释放给管道写入器。 / p>

现在,我当然可以创建一个新的字节数组并复制来自管道写入器的数据,但这会达到无复制的目的吗?因此,据我了解,我将需要在管道和通道之间进行一些缓冲区同步? 我观察了管道读取器的可用API( Advanceto ),它可以告诉管道读取器消耗了什么,检查了什么,但是却无法解决如何在管道读取方法之外进行同步。 / p>

所以问题是是否有一些技术或示例来实现这一目标。

解决方法

TryRead / ReadAsync获得的缓冲区仅在调用AdvanceTo之前有效,并且 expectation 会在您执行以下操作后立即执行以下操作:您报告为已消耗的任何物品都可以回收再利用(可能是并行/并行读取器)在其他地方使用。严格来说:即使您没有报告为已消耗的位,也仍然不应将其称为AdvanceTo(尽管实际上,它们可能是仍将是相同的段-只是:这不是呼叫者所关心的;对于呼叫者,它仅在读取和高级之间有效。

这意味着您明确不能:

while (...)
{
    var result = await pipe.ReadAsync();
    if (TryIdentifyFrameBoundary(out var frame)) {
        BeginProcessingInBackground(frame); // <==== THIS IS A PROBLEM!
        reader.AdvanceTo(frame.End,frame.End);
    }
    else if { // take nothing
        reader.AdvanceTo(buffer.Start,buffer.End);
        if (result.IsCompleted) break; // that's all folks
    }
}

因为“后台”位在触发时可能正在读取其他人的数据(由于已经被重用)。

因此:要么您需要将帧内容作为读取循环的一部分进行处理,您将必须复制数据,大多数可能通过使用:

c#
var len = checked ((int)buffer.Length);
var oversized = ArrayPool<byte>.Shared.Rent(len);
buffer.CopyTo(oversized);

,然后将oversized传递到您的后台处理程序,切记只能查看其中的前len个字节。您可以 作为ReadOnlyMemory<byte>进行传递,但是您需要考虑到以后还要将其返回到数组池(可能在finally中)块),并将其作为内存传递会使它更加笨拙(但并非不可能,这要感谢MemoryMarshal.TryGetArray)。


注意:在管道API的早期版本中,有一个引用计数元素, did 允许您保留缓冲区,但存在一些问题:

  • 这使API大大复杂化
  • 它导致缓冲区泄漏
  • 这是模棱两可的,混淆了“保留”的含义;直到被重新使用的计数?或完全发布

因此该功能已被删除。