Подтвердить что ты не робот

Создает ли С# AsyncCallback новый поток?

Я написал HttpListener, который прослушивает один из портов:

httpListener.BeginGetContext(new AsyncCallback(ListenerCallback), httpListener);

ListenerCallback обрабатывает любой запрос, полученный на uri-получателя. Если исключение возникает во время запроса обработки, оно запускает процедуру диагностики, которая пытается атаковать прослушиватель uri, чтобы проверить, действительно ли слушатель жив и прослушивает uri, и записывает журнал ответа, возвращаемого слушателем. Listener просто возвращает строку Listening... к таким фиктивным запросам.

Теперь во время тестирования, когда исключение произошло в других модулях, которые привели к выполнению диагностических модулей, я могу видеть, как слушатель вернул Listening... правильно, когда я проверил журналы. Однако, когда исключение произошло в ListenerCallback, попытка ударить URI-получателя внутри диагностики привела к следующему исключению:

System.Net.WebException : The operation has timed out
   at System.Net.HttpWebRequest.GetResponse()
   at MyPackage.Diagnostics.hitListenerUrl(String url) in c:\SW\MyApp\MyProj\Diagnostics.cs:line 190

Эта строка 190 в диагностическом модуле выглядит следующим образом:

189     HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
190     HttpWebResponse response = (HttpWebResponse)request.GetResponse();

Теперь, если AsyncCallback отправляет новый поток и запускает ListenerCallback в этом новом потоке, он не должен вызывать Operation Timeout, когда фиктивный запрос отправляется через диагностику. Это то, что я думал, что желаемое поведение должно быть, так как оно *Async*Callback. На самом деле MSDN также говорит то же:

Используйте делегат AsyncCallback для обработки результатов асинхронной операции в отдельном потоке.

Но, похоже, это не так. Я что-то упустил?

Интерпретация визуально:

enter image description here

4b9b3361

Ответ 1

Это полностью деталь реализации метода BeginXxx(). Существуют две основные схемы:

  • BeginXxx() запускает поток, чтобы выполнить задание, этот поток выполняет обратный вызов
  • BeginXxx() просит операционную систему выполнить задание, используя порт завершения ввода-вывода, чтобы попросить, чтобы его уведомили, когда это будет сделано. ОС запускает поток для доставки уведомления, которое выполняет обратный вызов.

Второй подход очень желателен, он хорошо масштабируется, когда программа может иметь множество ожидающих операций. И это подход, используемый HttpListener, стек стека TCP/IP в Windows поддерживает порты завершения. Ваша программа может поддерживать тысячи сокетов легко, важно в сценариях сервера.

В вызове EndXxx() в обратном вызове сообщается о любых неудачах, возникших при попытке выполнить запрос ввода-вывода, путем исключения исключения. В вашем случае BeginGetContext() требует EndGetContext() в обратном вызове. Если вы не поймаете исключение, ваша программа завершится.

Ваш фрагмент кода фактически не демонстрирует асинхронный ввод-вывод. Вы вызывали GetResponse() вместо BeginGetResponse(). Обратный вызов вообще не задействован, поэтому метод GetResponse() будет терпеть неудачу и выдает исключение.

Ответ 2

Создает ли С# AsyncCallback новый поток?

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

Обычно (но не обязательно) он вызывается в случайном потоке ThreadPool, который обрабатывал завершение операции ввода-вывода основного сокета (для получения более подробной информации здесь читается: Нет нити).

Используйте делегат AsyncCallback для обработки результатов асинхронная операция в отдельном потоке.

Я считаю, что это означает, что вы должны обработать результаты асинхронной операции в потоке, на который вызван ваш обратный вызов. Этот поток почти всегда отделен от потока, который инициировал операцию, как объяснялось выше. Это также делает их пример кода. Обратите внимание, что они не создают никаких новых потоков внутри ProcessDnsInformation.

После того, как вы получили обратный вызов, вам решать, как организовать файловую модель вашего серверного приложения. Главная проблема заключается в том, что она должна оставаться гибкой и масштабируемой. В идеале вы должны обслуживать входящий запрос в том же потоке, в который он поступает, и освобождать этот поток, как только вы это сделали, с любым заданием, связанным с ЦП, необходимым для обработки запроса. Как часть логики обработки, вам может потребоваться выполнить другие задачи, связанные с IO (файлы доступа, выполнить запросы БД, вызвать веб-службы и т.д.). При этом вы должны использовать асинхронные версии соответствующих API как можно меньше, чтобы избежать блокировки потока запросов (опять же, обратитесь к Нет ни одной темы).

IMO, с шаблоном асинхронной модели программирования (APM), который вы выбрали, реализация такой логики может быть довольно утомительной задачей ( особенно, часть обработки ошибок и восстановления).

Однако, используя Параллельная библиотека задач (TPL), async/await pattern и Task на основе API, например HttpListener.GetContextAsync, это кусок пирога. Лучшая часть: вам больше не нужно беспокоиться о многопоточности.

Чтобы дать вам представление о том, что я говорю, вот пример низкоуровневого TCP-сервера. Очень похожую концепцию можно использовать при внедрении HTTP-сервера HttpListener.

Ответ 3

Чтобы добавить отличный ответ Ганса,

Даже когда выполняется асинхронная операция, на самом деле возможно выполнить синхронно - даже в схеме # 2, неверно сказать, что обратные вызовы возвращаются в другой поток. Подсчет этого поведения может закончиться тем, что вы получаете взаимоблокировки или переполняете свой стек.

Вы можете проверить свойство IAsyncResult.CompletedSynchronously, чтобы определить это. Если установлено значение true, очень вероятно, что обратный вызов завершения пришел в тот же поток, который запустил операцию async: обратный вызов будет выполняться во время вашего вызова Begin* и должен завершиться до его возврата.