We have a fairly large existing code base for various webservices built on top of ASP.NET and that code makes heavy use of accessing HttpContext.Current.User
(wrapped as Client.User
) which I'm fairly sure internally uses [ThreadStatic]
to give you that ambient scoping.
I'm currently looking into if it's possible that we start to use more asynchronous code in the form of async/await
but I'm having a hard time finding how the use of [ThreadStatic]
fits into this. Removing the reliance on [ThreadStatic]
isn't really possible due to its heavy use.
It's my understanding that when an await
is hit, the execution of the code stops there, the call immediately returns and that a continuation is set up to continue the execution when the asynchronous code returns. Meanwhile, the original thread is free to be used for something else, for example to handle another request. So far my understanding of it.
What I can't really find a definitive answer to is whether or not HttpContext.Current.User
is guaranteed to be the same before and after the await
.
So basically:
HttpContext.Current.User = new MyPrincipal();
var user = HttpContext.Current.User;
await Task.Delay(30000);
// Meanwhile, while we wait for that lots of other requests are being handled,
// possibly by this thread.
Debug.Assert(object.ReferenceEquals(HttpContext.Current.User, user));
Is that Debug.Assert
guaranteed to succeed?
If another request was handled by the same thread as the Task.Delay
was pending, that request will have set a different HttpContext.Current.User
, so is that previous state somehow stored and restored when the continuation is called?
What I can imagine happening is that behind the scenes the [ThreadStatic]
state is kept as some sort of dictionary on the thread itself and that when a thread returns to the thread pool after returning on that await
that dictionary is kept safe somewhere and set back onto the thread when it executes the continuation (or on a thread, I'm not sure if it's necessarily even the same thread that handles the continuation), probably with an encouraging pat on the butt and a "go get 'em boy!", but that last part might just be my imagination.
Is that somewhat accurate?
UPDATE: I've tried to put together a small test that attempts this. So far it seems to work and the assertion hasn't failed for any out of hundreds of requests. Can anyone verify if the test makes sense?
async
/await
are thread-agnostic, meaning that they have a convention that can work within multiple different threading systems.
In general ThreadStatic
will not work correctly in async
/await
, except for trivial cases such as UI contexts where await
will resume on the UI thread. For ASP.NET, ThreadStatic
is not compatible with async
.
However, HttpContext.Current
is a special case. ASP.NET defines a "request context" (represented by an AspNetSynchronizationContext
instance assigned to SynchronizationContext.Current
). By default, await
ing a task will capture this synchronization context and use it to resume the method. When the method resumes, it may be on a different thread, but it will have the same request context (including HttpContext.Current
as well as other things such as culture and security).
So, HttpContext.Current
is preserved, but any of your own ThreadStatic
values are not.
I describe how await
works with SynchronizationContext
in my async
intro. If you want more details, check out my SynchronizationContext
MSDN article (particularly the last section on the async CTP).