To what extent are HttpApplication async events (eg those registered using AddOnEndRequestAsync
and friends) asynchronous? Does IIS wait for all asynchronous events fired for a particular event to complete before moving onto the next event, or are they 'fire and forget'?
It's not clear to me the exact workings when running in integrated pipeline mode, but I can tell you what I see for the non-integrated case, and the semantics should remain the same.
The short answer is that each event handler is fired in series, whether synchronous or asynchronous, and the next is not fired until the previous completes.
You can trace this through the source code.
A request comes in and is stored in a queue. Typically, when the HttpRuntime
dequeues a request, it initializes an HttpApplication
for the request by calling its InitInternal
method, passing the HttpContext
as an argument.
HttpApplication.InitInternal
initializes a new HttpApplication.ApplicationStepManager
class for the non-integrated-mode case. You can then see it calls the BuildSteps
method on it. This creates an ArrayList
to store the steps and constructs and stores all of the steps. Specifically, these steps are implementations of an IExecuteStep
interface. Ultimately, when all of the steps are added, the list is finalized by copying it to an array and saving it for later in a member var _execSteps
.
There are several sources for the steps, but the most used you'll see is the HttpApplication.CreateEventExecutionSteps
which takes the event type (begin request, authorize, etc.) and the steps array to add its steps for that event in. If you drill into CreateEventExecutionSteps
you can see it adding an IExecuteStep
for each async and sync handler it knows about, from the AsyncEvents
and Events
tables, respectively. The IExecuteStep
interface itself basically consists of an Execute
method and a CompletedSynchronously
flag.
Now, pause and look back at one of those Add methods like the one you mentioned, AddOnEndRequestAsync
, and you can see it add info about the async handler to the AsyncEvents
table. CreateEventExecutionSteps
will then walk through this table and an AsyncEventExecutionStep
will be constructed for each handler added.
Back to the request flow. After the HttpRuntime
initializes the HttpApplication
for the request, it calls its BeginProcessRequest
method, which fires ResumeSteps
.
ResumeSteps
is the important one where you can see how the steps are used and what the wait strategy is in the async case. You can see it maintaining a _currentStepIndex
into the array of execution steps. Eventually you see it grab the next step from the array and call its Execute
method. If the step reports that its execution CompletedSynchronously
, it loops and goes again. If not, it lets the method complete and enters into the async abyss.
To see what happens in this async case, you have to look at that AsyncEventExecutionStep
implementation which was created for the async handlers. In its Execute
implementation, you see it fire the begin handler and pass in a completion callback. In the constructor, you see this callback initialized to a method that eventually calls... HttpApplication.ResumeSteps
again!
And so it keeps going, executing steps, sync or async, until the array is overrun at which point it "finishes" the request processing.
The point is, you can clearly see that steps, which translate to the event handlers you add, are executed one by one, and whether sync or async, the following steps are not executed until the current step completes. Your question was whether events are handled one-by-one in this way, but as you can see, in fact it's even more granular, with each event handler being handled this way, so each gets synchronized access to the HttpContext and can operate without worrying about whether they are still "in the right phase" of the pipeline.
Obviously there's other details in that source code, yada yada, but this is the gist.
来源:https://stackoverflow.com/questions/9413585/do-asynchronous-httpapplication-events-wait-until-they-return