Потратив гораздо больше времени, чем кажется разумным, чтобы найти ответ на этот простой вопрос, я решил оставить свои результаты здесь, чтобы другим не пришлось прыгать через все обручи и неправильные пути, которыми я только что следовал.
Проблема в том, что если вы используете свойство TcpClient ReadTimeout и ваша операция чтения фактически истекает, Microsoft решила закрыть сокет. Это не ожидается, нежелательно, не выполняется ни одной другой известной мне реализацией сокета, и не имеет веских причин, по которым это должно иметь место, кроме лени программиста. Но это то, что Microsoft решила сделать.
В любом случае, все обходные пути, которые я нашел, в том числе на этом сайте, имели различные способы выполнения той или иной формы опроса занятости, а некоторые даже включали запуск еще одного потока для выполнения простого вызова чтения. Извините, но у меня есть дела поважнее с моим процессором, чем сидеть и опрашивать, особенно когда открыто много сокетов, так что это не мой вариант. В конце концов, это не начало 1990-х годов, когда опросы были просто способом, которым вы занимались. В настоящее время у нас есть такая вещь, как операционные системы, которые довольно эффективно заботятся о таких вещах, используя прерывания.
В любом случае, при еще одном косвенном поиске я наткнулся на этот старый пост в блоге:
http://blogs.msdn.com/b/mflasko/archive/2006/02/20/535655.aspx
Блоги MSDN > Блог Майка Фласко > Обработка тайм-аута при чтении сетевых данных
Ключевые выводы, которые подскажут вам, как правильно обрабатывать тайм-ауты чтения:
В этот момент может возникнуть соблазн перехватить исключение, а затем повторно выполнить чтение в том же NetworkStream. Эта стратегия может привести к неожиданным ошибкам. Теперь лучше всего рассматривать NetworkStream (сокет) как находящийся в нестабильном состоянии. Это связано с тем, что когда базовый стек истекает, базовое чтение ввода-вывода отменяется. Если данные поступают одновременно, они будут потеряны, что приведет к повреждению потока данных.
и решение:
Лучший подход — перехватить исключение, закрыть сокет или TCPClient и при необходимости переподключиться.
Хотя я все еще думаю, что это создает ненужную нагрузку на пользователя API, по крайней мере, это наиболее правильное решение, которое я смог найти из десятков сайтов, которые я просматривал, пытаясь понять, как сделать полуправильный ReadTimeout сокета.
Я надеюсь, что этот вопрос / комментарий сэкономит кому-то часы, которые мне потребовались, чтобы найти.