В книге Албахари "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
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
Комментариев нет:
Отправить комментарий