Мне непонятно, как работает при работе в режиме интегрированного конвейера, но я могу рассказать вам, что я вижу для неинтегрированного случая, и семантика должна остаться прежней.
Краткий ответзаключается в том, что каждый обработчик событий запускается последовательно, будь то синхронный или асинхронный, а следующий не запускается, пока не завершится предыдущий.
Вы можете отследить это через исходный код.
Приходит запросв и хранится в очереди.Как правило, когда HttpRuntime
отменяет запрос, он инициализирует HttpApplication
для запроса, вызывая его метод InitInternal
, передавая HttpContext
в качестве аргумента.
HttpApplication.InitInternal
инициализирует новый HttpApplication.ApplicationStepManager
класс для случая неинтегрированного режима.Затем вы можете увидеть, что он вызывает метод BuildSteps
.Это создает ArrayList
для хранения шагов и конструкций и сохраняет все шаги.В частности, эти шаги являются реализациями интерфейса IExecuteStep
.В конечном итоге, когда все шаги добавлены, список завершается путем копирования его в массив и сохранения его для последующего использования в элементе var _execSteps
.
. Существует несколько источников для шагов, но большинствовы увидите, что HttpApplication.CreateEventExecutionSteps
принимает тип события (начальный запрос, авторизация и т. д.) и массив шагов, чтобы добавить его шаги для этого события. Если вы углубитесь в CreateEventExecutionSteps
, вы увидите, что он добавляетIExecuteStep
для каждого асинхронного и синхронизирующего обработчика, о котором он знает, из таблиц AsyncEvents
и Events
соответственно.Сам интерфейс IExecuteStep
в основном состоит из метода Execute
и флага CompletedSynchronously
.
Теперь остановитесь и посмотрите на один из этих методов Add, таких как упомянутый вами, AddOnEndRequestAsync
, ивы можете увидеть, как он добавляет информацию об асинхронном обработчике в таблицу AsyncEvents
.CreateEventExecutionSteps
будет затем проходить через эту таблицу, и для каждого добавленного обработчика будет построено AsyncEventExecutionStep
.
Вернуться к потоку запросов.После того, как HttpRuntime
инициализирует HttpApplication
для запроса, он вызывает метод BeginProcessRequest
, который вызывает ResumeSteps
.
ResumeSteps
- это важный метод, в котором вы можете увидеть, как используются шагии какова стратегия ожидания в асинхронном случае.Вы можете видеть, что он поддерживает _currentStepIndex
в массиве шагов выполнения.В конце концов вы видите, что он получает следующий шаг из массива и вызывает его метод Execute
.Если шаг сообщает, что его выполнение CompletedSynchronously
, он зацикливается и возвращается снова.Если нет, то он позволяет методу завершиться и входит в асинхронную пропасть.
Чтобы увидеть, что происходит в этом асинхронном случае, вы должны взглянуть на реализацию AsyncEventExecutionStep
, созданную для асинхронных обработчиков.В его реализации Execute
вы видите, что он запускает обработчик begin и передает обратный вызов завершения.В конструкторе вы видите этот обратный вызов, инициализированный для метода, который в конечном итоге снова вызывает ... HttpApplication.ResumeSteps
!
И поэтому он продолжает работать, выполняя шаги, синхронизацию или асинхронность, пока массив не будет переполнен в этой точке.он «заканчивает» обработку запроса.
Дело в том, что вы можете ясно видеть, что шаги, которые преобразуются в добавленные вами обработчики событий, выполняются один за другим, и выполняются ли следующие шаги: синхронизация или асинхронность:не выполняется до завершения текущего шага.Ваш вопрос состоял в том, обрабатываются ли события по одному таким образом, но, как вы можете видеть, на самом деле это еще более детально, так как каждый обработчик событий обрабатывается таким образом, поэтому каждый получает синхронизированный доступ к HttpContext и может работать безволнуясь о том, находятся ли они все еще в «правильной фазе» конвейера.
Очевидно, что есть другие детали в этом исходном коде, yada yada, но это суть.