2014-01-09 4 views
13

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

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

ListenerCallback обрабатывает любой запрос, принимаемый на слушателя URI. Если исключение возникает во время запроса обработки, оно запускает процедуру диагностики, которая пытается атаковать прослушиватель uri, чтобы проверить, действительно ли слушатель жив и прослушивает uri, и записывает журнал ответа, возвращаемого слушателем. Слушатель просто возвращает строку 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 также says the same:

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

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

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

enter image description here

+1

«фиктивный запрос отправлен через диагностику» не совсем ясен. Кажется, вы спрашиваете о своей собственной теории, и это может быть очень плохо. –

+0

@HenkHolterman Я положил эту строку 190 диагностики, показывающую, как я отправляю запрос слушателю uri. – Mahesha999

+0

Итак, разбитый слушатель останавливает прослушивание ... –

ответ

13

Это полностью деталь реализации метода BeginXXX() класса. Есть две основные схемы:

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

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

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

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

+0

ohkay теперь я думаю, что понял. Но позаботимся еще немного информации/ссылок о том, как порты ввода-вывода IO используются для обработки обратных вызовов HttpListener. Я думаю, мне нужно прочитать некоторые материалы, может быть некоторые детали низкого уровня работы портов ввода-вывода IO и потоков Windows Kernel. Но по-прежнему оцените некоторые связанные ссылки, если у вас есть. (Связывание [this] (http://stackoverflow.com/a/4841725/1317018) ответа, содержащего несколько связанных материалов.) – Mahesha999

+0

Они довольно продвинутая концепция, по-настоящему понимающая их требует базовых знаний о том, как работают драйверы устройств. Много книг об этом, Русинович «Windows Internals» отлично. –

6

Создает ли C# AsyncCallback новую тему?

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

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

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

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

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

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

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

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

2

Чтобы добавить к превосходному ответу Ганса,

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

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

+0

Очень хороший и важный момент. В этом случае на самом деле может быть хорошей идеей продолжить обработку результата операции в другом потоке (используя «ThreadPool.QueueUserWorkItem' или« Task.Run »). – Noseratio

+0

@Noseratio Yess что-то вроде [этого] (http://stackoverflow.com/a/9035023/1317018) правильно? – Mahesha999

+0

@ Mahesha999, somethings like [that] (http://stackoverflow.com/a/9035023/1317018) действительно работает, но он блокирует поток из 'ThreadPool' синхронным вызовом GetContext. Я бы пошел с 'HttpListener.GetContextAsync', которому не нужен выделенный поток. Вам действительно не нужно овладевать внутренними компонентами ядра, чтобы использовать асинхронные API. – Noseratio

Смежные вопросы