I'm trying to mix AsyncController with dependency injection. The MVC app in question gets nearly all of its data through async web service calls. We are wrapping the async work in Tasks from the TPL, and notifying the controller's AsyncManager when these tasks complete.
Sometimes we have to touch the HttpContext in continuations of these tasks - adding a cookie, whatever. The correct way to do this according to Using an Asynchronous Controller in ASP.NET MVC is to call the AsyncManager.Sync
method. This will propagate the ASP.NET thread context, including the HttpContext, to the current thread, execute the callback, then restore the previous context.
However, that article also says:
Calling Sync() from a thread that is already under the control of ASP.NET has undefined behavior.
This isn't a problem if you do all of your work in the controller, since you generally know what thread you should be on in the continuations. But what I'm trying to do is create a middle layer in between our async data access and our async controllers. So these are also async. Everything is wired up by a DI container.
So like before, some of the components in a call chain will need to work with the "current" HttpContext. For example, after logon we want to store the "session" token we get back from a single-sign-on service. The abstraction for the thing that does that is ISessionStore. Think of a CookieSessionStore that puts a cookie on the response, or grabs the cookie from the request.
Two problems I can see with this:
To solve #1, I am basically injecting an object that grabs TaskScheduler.FromCurrentSynchronizationContext()
at the beginning of the request, and can invoke an action via a Task started with that scheduler, taking the HttpContextBase as an argument.
That is, from my components, I can call something similar to:
MySyncObject.Sync(httpContext => /* Add a cookie or something else */);
I have not yet observed any problems with this, but I am concerned about problem #2. I have looked at both AsyncManager
and SynchronizationContextTaskScheduler
in Reflector, and they operate similarly, executing the callback on the ASP.NET SynchronizationContext
. And that scares me :)
I had a bit of hope when I saw that the task scheduler implementation will invoke the directly rather than going through the synchronization context if it's inlining. But unfortunately, this doesn't seem to happen through the normal Task.Start(scheduler)
code-path. Rather, tasks can be inlined in other circumstances, like if they are being waited upon before they start.
So my questions are:
Relying on thread-local statics is rarely a good idea. While HttpContext.Current
relies on that mechanism and it has worked for years, now that we're going async, that approach is quickly deteriorating. It's much better to capture the value of this static as a local variable and pass that around with your async work so you always have it. So, for example:
public async Task<ActionResult> MyAction() {
var context = HttpContext.Current;
await Task.Yield();
var item = context.Items["something"];
await Task.Yield();
return new EmptyResult();
}
Or even better, avoid HttpContext.Current
altogether if you're in MVC:
public async Task<ActionResult> MyAction() {
await Task.Yield();
var item = this.HttpContext.Items["something"];
await Task.Yield();
return new EmptyResult();
}
Arguably, your middleware business logic especially shouldn't be relying on HttpContext or anything else in the ASP.NET libraries. So assuming your middleware calls back out to your controller (via callbacks, interfaces, etc.) to set cookies, you'll then have this.HttpContext
available to use for accessing that context.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With