问题
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?
https://gist.github.com/anonymous/72d0d6f5ac04babab7b6
回答1:
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).
回答2:
ThreadStatic is not at odds per se with asynchronous code at all. It is often used in that context to improve performance, by giving each thread its own copy of a specific object, eliminating contention.
Of course, it has to be used carefully. And if you have a scenario where you require the same object instance to be used regardless of which thread the code executes in, then ThreadStatic either won't work, or it will require careful handling of the threads to ensure that each flow of execution comes back to the thread where it belongs.
In some async/await scenarios, you are guaranteed that the continuation happens on the same thread as the original await. For example, when you await from within a GUI thread in a Forms or WPF program. But this is not guaranteed by the async/await features in general.
Ultimately, while you can use ThreadStatic with async/await, you still need to make sure you're using it in the way it's meant: to tie a specific value or object to a specific thread. This is great for general-purpose objects where any given flow of execution that might be continued doesn't care which object it actually uses. Or where you're sure the flow of execution remains in a given thread.
But otherwise, no. You don't want to be using ThreadStatic in scenarios where at the same time you need the flow of execution to always use the same object, but you cannot guarantee that the flow of execution will remain on the same thread. (Sorry if that last statement seems obvious…I just want to make sure it's clear).
回答3:
Assuming the await
was called from a async-friendly call stack (async controller / handler) then, yes, the assert is guaranteed to succeed.
The ASP.NET SynchronizationContext will handle making HttpContext available on return threads (unless you use ConfigureAwait(false)
). However this does not apply to thread data in general, so prefer HttpContext.Items if you have that type of "request global" state.
来源:https://stackoverflow.com/questions/26516730/is-the-use-of-threadstatic-at-odds-with-asynchronous-code