Как я могу разделить (скопировать) поток в .NET? - PullRequest
8 голосов
/ 29 июня 2009

Кто-нибудь знает, где я могу найти реализацию разделителя потока?

Я хочу взять поток и получить два отдельных потока, которые можно независимо читать и закрывать, не влияя друг на друга. Каждый из этих потоков должен возвращать те же двоичные данные, что и исходный поток. Не нужно реализовывать Позицию или Поиск и все такое ... Только вперед.

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

Есть ли что-нибудь, что могло бы сделать это?

Ответы [ 7 ]

4 голосов
/ 29 июня 2009

Не из коробки.

Вам необходимо будет буферизовать данные из исходного потока в формате FIFO, отбрасывая только те данные, которые были прочитаны всеми потоками «чтения».

Я бы использовал:

  • Объект "управления", содержащий какую-то очередь байтов [], в которой хранятся фрагменты, подлежащие буферизации, и считывающие дополнительные данные из исходного потока, если это необходимо
  • Некоторые экземпляры «читателя», которые знают, где и в каком буфере они читают, и которые запрашивают следующий «чанк» у «управления» и уведомляют его, когда они больше не используют чанк, чтобы его можно было удалить из очередь
3 голосов
/ 29 июня 2009

Это может быть сложно, не рискуя хранить все буферизованные в памяти (если потоки имеют значения BOF и EOF соответственно).

Интересно, не проще ли записать поток на диск, скопировать его и получить два потока, считывающих с диска, с самоудалением, встроенным в Close() (т. Е. Написать свою Stream оболочку вокруг FileStream).

2 голосов
/ 04 ноября 2015

Представленное ниже действительное имя называется EchoStream http://www.codeproject.com/Articles/3922/EchoStream-An-Echo-Tee-Stream-for-NET Это очень старая реализация (2003), но она должна предоставлять некоторый контекст

найдено через Перенаправление записи в файл в поток C #

2 голосов
/ 29 июня 2009

Вы не можете сделать это без дублирования хотя бы части потока источника - в основном из-за того, что если не звучит так, как вы можете контролировать скорость, с которой они потребляются (несколько потоков?) Вы можете сделать что-то умное в отношении одного чтения, а не другого (и, следовательно, сделать копию только в этот момент), но сложность этого звучит так, будто это не стоит проблем.

1 голос
/ 03 ноября 2017

Я сделал SplitStream доступным на github и NuGet.

Это так.

using (var inputSplitStream = new ReadableSplitStream(inputSourceStream))

using (var inputFileStream = inputSplitStream.GetForwardReadOnlyStream())
using (var outputFileStream = File.OpenWrite("MyFileOnAnyFilestore.bin"))

using (var inputSha1Stream = inputSplitStream.GetForwardReadOnlyStream())
using (var outputSha1Stream = SHA1.Create())
{
    inputSplitStream.StartReadAhead();

    Parallel.Invoke(
        () => {
            var bytes = outputSha1Stream.ComputeHash(inputSha1Stream);
            var checksumSha1 = string.Join("", bytes.Select(x => x.ToString("x")));
        },
        () => {
            inputFileStream.CopyTo(outputFileStream);
        },
    );
}

Я не тестировал его на очень больших потоках, но попробую.

GitHub: https://github.com/microknights/SplitStream

1 голос
/ 29 июня 2009

Я не думаю, что вы сможете найти общую реализацию, чтобы сделать именно это. Поток довольно абстрактный, вы не знаете, откуда байт. Например, вы не знаете, будет ли это поддерживать поиск; и вы не знаете относительную стоимость операций. (Поток может быть абстракцией чтения данных с удаленного сервера или даже с ленты резервного копирования!).

Если вы можете создать MemoryStream и сохранить содержимое один раз, вы можете создать два отдельных потока, используя один и тот же буфер; и они будут вести себя как независимые потоки, но использовать память только один раз.

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

0 голосов
/ 28 апреля 2017

С введением async / await, поскольку все ваши задачи чтения, кроме одной, являются асинхронными, вы сможете обрабатывать одни и те же данные дважды, используя только один поток ОС.

Я думаю, что вы хотите, это связанный список блоков данных, которые вы видели до сих пор. Затем вы можете иметь несколько пользовательских экземпляров Stream, которые содержат указатель на этот список. Когда блоки попадают в конец списка, они будут собирать мусор. Немедленное повторное использование памяти потребовало бы некоторого другого вида циклического списка и подсчета ссылок. Выполнимо, но сложнее.

Когда ваш пользовательский поток может ответить на вызов ReadAsync из кэша, скопируйте данные, переместите указатель вниз по списку и вернитесь.

Когда ваш поток достиг конца списка кэша, вы хотите выполнить один ReadAsync для базового потока, не ожидая его, и кэшировать возвращенную задачу с помощью блока данных. Поэтому, если какой-либо другой считыватель Stream также догоняет и пытается прочитать больше до завершения этого чтения, вы можете вернуть тот же объект Task.

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

...