Справляется ли Stream.Copy?

Предположим, что я пишу прокси-код tcp. Я читаю из входящего потока и записываю в выходной поток. Я знаю, что Stream.Copy использует буфер, но мой вопрос: Метод Stream.Copy записывает в выходной поток, выбирая следующий фрагмент из входного потока, или он представляет собой цикл, подобный "чтение фрагмента из ввода, запись фрагмента для вывода, чтение фрагмента из ввода и т.д."?

Ответ 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);
    }
}

Итак, как вы можете видеть, он читается из источника, а затем записывается в пункт назначения. Вероятно, это может быть улучшено;)


EDIT: здесь возможна реализация версии с каналами:

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;
}

В принципе, он синхронно считывает блок, начинает асинхронно копировать его в пункт назначения, затем читает следующий фрагмент и ждет завершения записи.

Я провел несколько тестов производительности:

  • используя MemoryStream s, я не ожидал значительного улучшения, поскольку он не использует порты завершения ввода-вывода (AFAIK); и действительно, производительность почти идентична.
  • используя файлы на разных дисках, я ожидал, что версия с каналами будет работать лучше, но она не... на самом деле немного медленнее (на 5-10%).

Таким образом, это, по-видимому, не приносит никакой пользы, что, вероятно, является причиной того, что он не реализован таким образом...

Ответ 2

Согласно Reflector, это не так. Такое поведение лучше документировать, потому что оно представит concurrency. Это никогда не бывает безопасным в целом. Поэтому дизайн API, а не "труба", звучит.

Таким образом, речь идет не только о том, чтобы Stream.Copy быть более или менее умным. Копирование одновременно не является детализацией реализации.

Ответ 3

Stream.Copy - это синхронная операция. Я не думаю, что разумно ожидать, что он будет использовать асинхронное чтение/запись для одновременного чтения и записи.

Я ожидал бы асинхронную версию (например RandomAccessStream.CopyAsync) для одновременного чтения и записи.

Примечание. Использование нескольких потоков во время копирования было бы нежелательным поведением, но использование асинхронного чтения и записи для запуска их в одно и то же время было нормально.

Ответ 4

Запись в выходной поток невозможна (при использовании одного буфера) при извлечении следующего фрагмента, потому что выборка следующего фрагмента может перезаписать буфер, пока он используется для вывода.
Вы можете сказать, что используйте двойную буферизацию, но она почти такая же, как и буфер с двойным размером.