【问题标题】:Is Stream.Copy piped?Stream.Copy 是否通过管道传输?
【发布时间】:2012-09-08 11:00:12
【问题描述】:

假设我正在编写一个 tcp 代理代码。 我正在从传入流中读取并写入输出流。 我知道 Stream.Copy 使用缓冲区,但我的问题是: Stream.Copy 方法是在从输入流中获取下一个块的同时写入输出流还是像“从输入读取块,将块写入输出,从输入读取块等”这样的循环?

【问题讨论】:

  • 有趣的问题;我实际上不知道不检查。当然,需要注意的是,这样做需要 2 个单独的缓冲区(或单个缓冲区的两个单独部分)。
  • 是的,很明显需要双缓冲区。但我不确定 Stream.Copy 函数是否聪明。
  • 应该注意的是,如果它使用一个管道,那么它将进行两个流复制,这将涉及另外两个管道,...

标签: c# tcp stream


【解决方案1】:

这是CopyTo 在 .NET 4.5 中的实现:

private void InternalCopyTo(Stream destination, int bufferSize)
{
    int num;
    byte[] buffer = new byte[bufferSize];
    while ((num = this.Read(buffer, 0, buffer.Length)) != 0)
    {
        destination.Write(buffer, 0, num);
    }
}

如您所见,它从源读取,然后写入目标。这可能会得到改进;)


编辑:这是管道版本的可能实现:

public static void CopyToPiped(this Stream source, Stream destination, int bufferSize = 0x14000)
{
    byte[] readBuffer = new byte[bufferSize];
    byte[] writeBuffer = new byte[bufferSize];

    int bytesRead = source.Read(readBuffer, 0, bufferSize);
    while (bytesRead > 0)
    {
        Swap(ref readBuffer, ref writeBuffer);
        var iar = destination.BeginWrite(writeBuffer, 0, bytesRead, null, null);
        bytesRead = source.Read(readBuffer, 0, bufferSize);
        destination.EndWrite(iar);
    }
}

static void Swap<T>(ref T x, ref T y)
{
    T tmp = x;
    x = y;
    y = tmp;
}

基本上,它同步读取一个块,开始异步将其复制到目标,然后读取下一个块并等待写入完成。

我进行了一些性能测试:

  • 使用MemoryStreams,我没想到会有显着的改进,因为它不使用IO完成端口(AFAIK);事实上,性能几乎相同
  • 使用不同驱动器上的文件,我预计管道版本的性能会更好,但事实并非如此......它实际上稍微慢了一点(5% 到 10%)

所以它显然没有带来任何好处,这可能是它没有以这种方式实现的原因......

【讨论】:

  • 我相信缓冲区大小在使用文件时起着重要作用。
  • @IsraelLot,是的,可能。我使用的默认缓冲区大小与默认 Stream.Copy 实现中的相同。
  • 理想的解决方案是在两个缓冲区之间交替:stackoverflow.com/a/4139427/648265
  • @ivan_pozdeev,您在我的代码中的编辑不正确,导致将空缓冲区写入输出流。我把它滚了回来。请不要对其他人的代码进行重大修改,除非只是为了修复明显的语法错误或拼写错误。
  • @ThomasLevesque:我只是不喜欢参考杂耍。 C 风格的数组索引技术看起来更加精简。
【解决方案2】:

Stream.Copy 是同步操作。我认为期望它使用异步读/写来进行同时读写是不合理的。

我希望异步版本(如RandomAccessStream.CopyAsync)使用同时读写。

注意:在复制过程中使用多个线程是不受欢迎的行为,但使用异步读写同时运行它们是可以的。

【讨论】:

  • 异步并不意味着并发。与适当的文档同时进行并且非常明确地说明它是非常危险的。默认情况下,几乎没有什么是线程安全的。
  • @usr,不确定您的评论是关于什么的……也许混淆了“并行”的用法?请参阅编辑(并行 -> 同时)。
  • 对不起,我的评论不清楚。提到这个:“我希望异步版本......到......同时读取和写入。”:RandomAccessStream.CopyAsync 不能同时执行此操作,因为这可能会在用户/框架代码中引入竞争条件。读写都可以访问共享变量等。
  • @usr 什么竞争条件?如果支持异步读/写操作的流(4.5 的 BeginXXX/EndXXX 对 XXXAsync)会显式地促使它们具有竞争条件,我会感到非常惊讶。现在对于用户的代码 - 异步操作将在启动时在同一个线程上回调,这里没有并发问题。我可以看到的唯一直接问题是相同的缓冲区是否用于目标或读取和写入源,但只有调用异步方法的人明确共享它才是问题......
  • 读取和写入调用可能在自定义流实现上运行。例如,它们可能会增加“静态 int readWriteCount”变量。如果同时调用,则存在种族。再说一遍:异步并不意味着并行。出于这个原因,异步读写调用在内部不能是线程安全的。他们可能希望一次在一个线程上运行(尽管随着时间的推移在不同的线程上)。
【解决方案3】:

根据 Reflector 的说法,它没有。这种行为最好记录下来,因为它会引入并发性。一般来说,这样做是不安全的。因此,不“管道”的 API 设计是合理的。

所以这不仅仅是Stream.Copy 或多或少聪明的问题。以并发方式复制不是实现细节。

【讨论】:

    【解决方案4】:

    在获取下一个块时无法写入输出流(使用一个缓冲区时),因为获取下一个块可能会在缓冲区用于输出时覆盖缓冲区。
    您可以说使用双缓冲,但它与使用双倍大小的缓冲区几乎相同。

    【讨论】:

    • 既然(你自己注意到)我们可以使用单独的缓冲区(或缓冲区的片段),这显然不是“不可能的”。
    猜你喜欢
    • 2017-10-12
    • 2014-08-17
    • 2016-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-18
    • 2012-04-18
    相关资源
    最近更新 更多