#c# #sockets #.net-core
#c# #сокеты #.net-ядро
Вопрос:
Я вижу некоторое неожиданное поведение, хотя и не обязательно вызывающее проблему, которое я хотел бы понять относительно программирования асинхронных сокетов.
Речь идет о выполнении mySocket.Вызов BeginConnect и его вызов из предоставленного AsyncCallback. С некоторым тестовым проектом для сетевого взаимодействия я вижу, что для одного и того же кода предоставленный обратный вызов иногда вызывается синхронно во время выполнения BeginConnect, а иногда и асинхронно (BeginConnect уже вернул IAsyncResult до завершения обратного вызова), причем последний случай является наиболее распространенным (80% — 90% случаев).
Однако я не могу воспроизвести синхронный обратный вызов с помощью очень простого тестового примера с соединением сокета tcp с клиентом и сервером, который просто рассылает спам по вызовам connect / disconnect.
Я проверил исходный код платформы .NET Core 2.2 и вижу, что базовый класс LazyAsyncResult имеет два конструктора, из которых один синхронно вызывает обратный вызов в своем конструкторе. Однако я не нахожу ни одного случая, когда этот конструктор использовался фреймворком. Вызовы, которые я выполнял с помощью отладчика, всегда, кажется, создают ContextAwareResult, который имеет только вызов базового конструктора для другого конструктора. Я могу в некоторых из этих тестовых случаев также четко видеть в отладчике, что свойство CompletedSynchronously возвращает true.
Есть какие-либо подсказки, в каком сценарии этот обратный вызов вызывается синхронно и почему?
Приветствия.
Редактировать:
- Тестирование проводилось в Windows 10.
- Точно такой же код на полной платформе .NET 4.6.2 всегда создает асинхронно.
Ответ №1:
Вы не указали ОС, которую используете для тестов, но предположим, что это Windows (для не-Windows я бы с высокой степенью уверенности сказал, что поведение должно быть похожим), это ожидаемое поведение асинхронных операций ввода-вывода. В частности, не гарантируется, что асинхронный запрос ввода-вывода всегда завершается асинхронно. Когда это возможно, диспетчер ввода-вывода пытается сократить путь операции ввода-вывода по очевидным причинам (хорошим примером являются быстрые операции ввода-вывода, которые обходят обычный поток ввода-вывода через драйверы устройств и напрямую связываются с диспетчером памяти — это в основном для файловых операций, которые могут быть обналичены). В этом случае результат может быть возвращен в том же потоке, независимо от того, что операция запрашивается как асинхронная. Для сетевого ввода-вывода такое поведение чаще всего встречается для операций чтения (в случаях, когда данные уже считаны во внутренний буфер), реже для операций записи (для TCP в основном для случаев, когда включен Nagle или отложенный ACK, когда завершение операции вывода не требует ожидания пакета подтверждения). Что касается сценария с открытием TCP-соединения, то это возможно, когда процедура подтверждения TCP пропускает сетевой уровень, как в случае опции SIO_LOOPBACK_FAST_PATH (подробнее см. здесь и здесь), которая, однако, может использоваться только для интерфейсных сокетов loopback (localhost).
Асинхронность ввода-вывода .NET построена поверх собственных механизмов ввода-вывода и наследует свойства и поведение этих механизмов.
Комментарии:
1. Спасибо за информацию, однако я хотел бы знать, почему это изменение в поведении между запусками в идентичных состояниях и конкретно для этого кода, поскольку я не могу объяснить это через исходный код. Более того, запуск точно такого же кода в .NET 4.6.2 выполняется всегда асинхронно.