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

209
26 июля 2018, 17:40

В книге Албахари "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<string> ReadLineAsync()
    {
        return await sr.ReadLineAsync();
    }
    public void Dispose()
    {
        if (sr != null) sr.Close();
        if (socket != null) socket.Dispose();
    }
}

Допустим, пришла строка "123\r\nqwe". StreamReader считает всю её в свой внутренний буфер при вызове ReadLineAsync() вернет "123". Данные из внутреннего буфера не удаляются же? При втором вызове ReadLineAsync() завершится, когда придет конец строки и в результатом вернется "qwe". Албахари пишет про бесконечное блокирование, так к нему может привести и обычный метод Read, если никакие данные не будут отправлены сокету.

Объясните почему нельзя использовать класс StreamReader вместе с NetworkStream?

Answer 1

"Буфер опережающего чтения" означает, что StreamReader попытается вычитать из потока сразу килобайт (bufferSize) данных. "Про запас". Это улучшает производительность в случае чтения с диска, но может приводить к непредсказуемым последствиям при чтении.

Особенность NetworkStream.Read в том, что он может блокироваться при чтении (в случае, если сокет еще открыт, но данных нет).

Албахари пишет о теоретической ситуации, когда

  • StreamReader мог бы вернуть вам данные (т.к. они есть в буфере),
  • но обнаружил, что в буфере есть место и решил на всякий случай дочитать побольше данных в буфер
  • вызвал NetworkStream.Read
  • NetworkStream.Read заблокировался

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

На практике (в текущей реализации StreamReader и NetworkStream) такая ситуация не возникает, т.к.

  • StreamReader читает данные из входного потока только в случае, если в буфере недостаточно данных для завершения требуемой операции.
  • NetworkStream.Read не блокируется, если хоть какие-то данные есть.

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

Но реализация — это одно, нет никаких гарантий что она не поменяется.

На самом деле проблема решается тем, что сочетание NetworkStream + StreamReader не имеет практической ценности. StreamReader приспособлен для работы с чисто текстовыми данными. Например, он пытается искать в читаемом потоке преамбулу (BOM), и вообще — использует сами данные для определения концов строк, что уже дико неудобно при работе с сетью.

По сети обычно передаются смешанные данные. Например, те же строки принято передавать в виде <длина><текст>, чтобы не попадать в ловушку "непонятно сколько данных вычитали и сколько еще ждать". Для такой работы со стримами удобнее использовать BinaryReader.

READ ALSO
Медленная отрисовка TreeView C#,

Медленная отрисовка TreeView C#,

Использую стандартный treeViewУзлы добавил через свойства компонента Nodes -> Edit Nodes

171
Тест TCP сервера

Тест TCP сервера

Как проверить сколько TCP соединений может нормально держать сервер? Как проверить производительность TCP сервера?

160
C# WPF ListView как изменить цвет и внешний вид шапки? [закрыт]

C# WPF ListView как изменить цвет и внешний вид шапки? [закрыт]

Подскажите как изменить внешний вид шапки с наименьшими затратами, чтобы у нее поставить свой цвет и чтобы у нее был плоский вид

457
Как связать коды скомпилированные на c++ и c#

Как связать коды скомпилированные на c++ и c#

Я задавал похожий а вопрос, но я спрашивал про компиляцию c++ вместе с c#Немного разобравшись стало понятно, что компилирование двух кодов просто...

172