TCP Socket ReceiveAsync иногда занимает слишком много времени - PullRequest
0 голосов
/ 26 июня 2019

Мне нужно работать с устройством, которое использует TCP-соединение для управления им. Он отправляет 1 байт данных каждые 30 миллисекунд, и я должен отреагировать на него как можно скорее. Обычно все работает нормально, но иногда функция Socket.ReceiveAsync () зависает на время до 400-800 миллисекунд и затем возвращает некоторое количество полученных байтов.

Я использую такой код:

_socket.ReceiveTimeout = 5;
var sw = Stopwatch.StartNew();
var len = await _socket.ReceiveAsync(new ArraySegment<byte>(Buffer, Offset, Count),
                                     SocketFlags.None)
                       .ConfigureAwait(false);
_logger.Info($"Reading took {sw.ElapsedMilliseconds}ms");  // usually 0-6ms, but sometimes up to 800ms

Я записал этот процесс с помощью Wireshark и увидел, что все данные были получены вовремя, с интервалом около 30 мс.

Я также заметил, что вероятность возникновения этой задержки выше, когда вы что-то делаете на своем компьютере. Как открыть меню «Пуск» или Explorer. Я думаю, что переключение на другой процесс или сборщик мусора должно быть намного быстрее.

1 Ответ

1 голос
/ 26 июня 2019

Он отправляет 1 байт данных каждые 30 миллисекунд, и я должен отреагировать на него как можно скорее.

Это крайне трудно сделать в Windows, которая не является операционной системой реального времени. На самом деле, все, что вы можете сделать, - это сделать все возможное, понимая, что неожиданное антивирусное сканирование может сойти с него.

Однажды меня отправили на сайт клиента, чтобы выяснить тайм-ауты связи TCP / IP, которые произошли только ночью. Полет на самолете, проживание в отеле, весь Шебанг. Оказывается, ночная смена играла в DOOM. Правдивая история.

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

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

Второе, что вы можете сделать, это уменьшить накладные расходы. В этом случае вы вызываете Socket API, который возвращает Task<T>; было бы лучше назвать тот, который возвращает ValueTask<T>:

var len = await _socket.ReceiveAsync(
    new ArraySegment<byte>(Buffer, Offset, Count).AsMemory(), SocketFlags.None)
    .ConfigureAwait(false);

См. в этом блоге , чтобы узнать, как ValueTask<T> уменьшает использование памяти, особенно с сокетами.

В качестве альтернативы, вы можете сделать цикл чтения запущенным синхронно в отдельном потоке, как указано в комментариях. Приятным аспектом для отдельного потока является то, что вы можете повысить его приоритет даже в диапазоне «в реальном времени». Но есть драконы - вы действительно не хотите этого делать, если у вас нет буквального выбора. Если в этом потоке есть какая-либо ошибка, вы можете заблокировать всю ОС.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...