3

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

JulianR
  • 16,213
  • 5
  • 55
  • 85
  • `Is that Debug.Assert guaranteed to succeed?` No, Better try it yourself.. – L.B Oct 22 '14 at 20:44
  • 1
    Related wrt what is "flowed" - http://stackoverflow.com/questions/22416182/is-await-supposed-to-restore-thread-currentcontext , http://stackoverflow.com/questions/12456115/understanding-context-in-c-sharp-5-async-await – user2864740 Oct 22 '14 at 20:47
  • @L.B Well, considering it's asynchronous/multithreaded code, writing a test that proves it works/doesn't work is probably kinda tricky. Then you run the risk of 'proving' that it works in 99.999% of cases. I'd rather just have an insight into how it actually works. What makes you say "no"? – JulianR Oct 22 '14 at 20:50
  • 1
    @JulianR: it's easy enough to demonstrate the counter-example: just use async/await in a console program where there's no synchronization context. Then a continuation has no choice except to run on a different thread. – Peter Duniho Oct 22 '14 at 20:52
  • 1
    http://stackoverflow.com/questions/13010563/using-threadstatic-variables-with-async-await – user2864740 Oct 22 '14 at 20:53
  • @Julian: I suspect that in this case, you could rig up a test that showed it *didn't* work fairly easily. As you say, positives in threading tests are sadly not usually very meaningful (because they can be false positives), but there's no such thing as a false negative in this context ("if it can happen, it will"). – Cameron Oct 22 '14 at 20:54
  • @JulianR `What makes you say "no"?` I tried it in WCF with `WebOperationContext.Current`, and it didn't work. Seeing a single case that doesn't work makes me conclude as NO. – L.B Oct 22 '14 at 20:56

3 Answers3

8

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, awaiting 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).

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
1

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).

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • 1
    Okay, thanks. But it could still be that depending on how ASP.NET assigns its threads, that ASP.NET *can* make that guarantee because (for example) a request is always executed on the same thread in a continuation? – JulianR Oct 22 '14 at 20:54
  • Yes, I do not know enough about ASP.NET to know how it manages its sessions. It is possible. But you would want to check the documentation to make sure that behavior is part of its _contract_. Because if it's not documented, then even if it's how it behaves currently, there's no guarantee it will remain that way in the future (though admittedly, Microsoft does usually work hard to try to avoid bad breaking changes like that). – Peter Duniho Oct 22 '14 at 20:55
1

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.

Richard Szalay
  • 83,269
  • 19
  • 178
  • 237
  • Okay, thanks, very useful because at some point we'll likely move to something not built on System.Web (project Helios/ASP.NET vNext) so for storing ambient user context we won't be able to just replace `HttpContext.Current` with any old `[ThreadStatic]` value somewhere. If we want to use async code, that is. – JulianR Oct 22 '14 at 21:32