Страницы

Поиск по вопросам

понедельник, 22 октября 2018 г.

Почему нельзя использовать StreamReader вместе с NetworkStream?

В книге Албахари "C# 6.0 in a Nutshell" написано следующее:
В действительности класс StreamReader абсолютно запрещено применять вместе с NetworkStream, даже если вы планируете вызывать только метод ReadLine. Причина в том, что класс StreamReader имеет буфер опережающего чтения, который может привести к чтению большего числа байтов, чем доступно в текущий момент, и бесконечному блокированию (или до возникновения тайм-аута сокета). Другие потоки, такие как FileStream, не страдают подобной несовместимостью с классом StreamReader, потому что они поддерживают определенный признак окончания, при достижении которого метод Read немедленно завершается, возвращая значение 0
Какая ошибка, например, в этом классе?
public class AsyncSocket : IDisposable { Socket socket; StreamReader sr;
public AsyncSocket() { socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); sr = new StreamReader(new NetworkStream(socket)); }
// ... остальные члены убраны
public async Task ReadLineAsync() { return await sr.ReadLineAsync(); }
public void Dispose() { if (sr != null) sr.Close(); if (socket != null) socket.Dispose(); } }
Допустим, пришла строка "123
qwe". StreamReader считает всю её в свой внутренний буфер при вызове ReadLineAsync() вернет "123". Данные из внутреннего буфера не удаляются же? При втором вызове ReadLineAsync() завершится, когда придет конец строки и в результатом вернется "qwe". Албахари пишет про бесконечное блокирование, так к нему может привести и обычный метод Read, если никакие данные не будут отправлены сокету.
Объясните почему нельзя использовать класс StreamReader вместе с NetworkStream?


Ответ

"Буфер опережающего чтения" означает, что StreamReader попытается вычитать из потока сразу килобайт (bufferSize) данных. "Про запас". Это улучшает производительность в случае чтения с диска, но может приводить к непредсказуемым последствиям при чтении.
Особенность NetworkStream.Read в том, что он может блокироваться при чтении (в случае, если сокет еще открыт, но данных нет).
Албахари пишет о теоретической ситуации, когда
StreamReader мог бы вернуть вам данные (т.к. они есть в буфере), но обнаружил, что в буфере есть место и решил на всякий случай дочитать побольше данных в буфер вызвал NetworkStream.Read NetworkStream.Read заблокировался
И вы оказываетесь в ситуации, когда вроде как нужные вам данные пришли, но StreamReader висит и не возвращает ничего, т.к. хочет еще больше данных из потока.
На практике (в текущей реализации StreamReader и NetworkStream) такая ситуация не возникает, т.к.
StreamReader читает данные из входного потока только в случае, если в буфере недостаточно данных для завершения требуемой операции. NetworkStream.Read не блокируется, если хоть какие-то данные есть.
Т.е. блокировка произойдет только в случае, когда нужные данные действительно не пришли.
Но реализация — это одно, нет никаких гарантий что она не поменяется.
На самом деле проблема решается тем, что сочетание NetworkStream + StreamReader не имеет практической ценности. StreamReader приспособлен для работы с чисто текстовыми данными. Например, он пытается искать в читаемом потоке преамбулу (BOM), и вообще — использует сами данные для определения концов строк, что уже дико неудобно при работе с сетью.
По сети обычно передаются смешанные данные. Например, те же строки принято передавать в виде <длина><текст>, чтобы не попадать в ловушку "непонятно сколько данных вычитали и сколько еще ждать". Для такой работы со стримами удобнее использовать BinaryReader

Комментариев нет:

Отправить комментарий