Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using ThreadStatic variables with async/await

With the new async/await keywords in C#, there are now impacts to the way (and when) you use ThreadStatic data, because the callback delegate is executed on a different thread to one the async operation started on. For instance, the following simple Console app:

[ThreadStatic] private static string Secret;  static void Main(string[] args) {     Start().Wait();     Console.ReadKey(); }  private static async Task Start() {     Secret = "moo moo";     Console.WriteLine("Started on thread [{0}]", Thread.CurrentThread.ManagedThreadId);     Console.WriteLine("Secret is [{0}]", Secret);      await Sleepy();      Console.WriteLine("Finished on thread [{0}]", Thread.CurrentThread.ManagedThreadId);     Console.WriteLine("Secret is [{0}]", Secret); }  private static async Task Sleepy() {     Console.WriteLine("Was on thread [{0}]", Thread.CurrentThread.ManagedThreadId);     await Task.Delay(1000);     Console.WriteLine("Now on thread [{0}]", Thread.CurrentThread.ManagedThreadId); } 

will output something along the line of:

Started on thread [9] Secret is [moo moo] Was on thread [9] Now on thread [11] Finished on thread [11] Secret is [] 

I've also experimented with using CallContext.SetData and CallContext.GetData and got the same behaviour.

After reading some related questions and threads:

  • CallContext vs ThreadStatic
  • http://forum.springframework.net/showthread.php?572-CallContext-vs-ThreadStatic-vs-HttpContext&highlight=LogicalThreadContext
  • http://piers7.blogspot.co.uk/2005/11/threadstatic-callcontext-and_02.html

it seems that frameworks like ASP.Net explicitly migrates the HttpContext across threads, but not the CallContext, so perhaps the same thing is happening here with the use of async and await keywords?

With the use of the async/await keywords in mind, what's the best way to store data associated with a particular thread of execution that can be (automatically!) restored on the callback thread?

Thanks,

like image 782
theburningmonk Avatar asked Oct 22 '12 11:10

theburningmonk


People also ask

Does async await use thread pool?

Await Keyword Basically, it returns to caller thread with reference to ongoing task and stop execution of code below that line and release the current thread to thread pool to process another request. Async and await are always used together, if not, then there will be something wrong.

Is .result same as await?

await asynchronously unwraps the result of your task, whereas just using Result would block until the task had completed. See this explanantion from Jon Skeet.

Does async await improve performance?

C# Language Async-Await Async/await will only improve performance if it allows the machine to do additional work.

Does await run synchronously?

async/await do not make synchronous code asynchronous. Instead, these keywords make it much easier to code continuations, eliminating ugly boilerplate code.


2 Answers

You could use CallContext.LogicalSetData and CallContext.LogicalGetData, but I recommend you don't because they don't support any kind of "cloning" when you use simple parallelism (Task.WhenAny / Task.WhenAll).

I opened a UserVoice request for a more complete async-compatible "context", explained in more detail in an MSDN forum post. It does not seem possible to build one ourselves. Jon Skeet has a good blog entry on the subject.

So, I recommend you use argument, lambda closures, or the members of the local instance (this), as Marc described.

And yes, OperationContext.Current is not preserved across awaits.

Update: .NET 4.5 does support Logical[Get|Set]Data in async code. Details on my blog.

like image 123
Stephen Cleary Avatar answered Sep 20 '22 15:09

Stephen Cleary


Basically, I would emphasize: don't do that. [ThreadStatic] is never going to play nicely with code that jumps between threads.

But you don't have to. A Task already carries state - in fact, it can do it 2 different ways:

  • there's an explicit state object, which can hold everything you need
  • lambdas/anon-methods can form closures over state

Additionally, the compiler does everything you need here anyway:

private static async Task Start() {     string secret = "moo moo";     Console.WriteLine("Started on thread [{0}]",         Thread.CurrentThread.ManagedThreadId);     Console.WriteLine("Secret is [{0}]", secret);      await Sleepy();      Console.WriteLine("Finished on thread [{0}]",         Thread.CurrentThread.ManagedThreadId);     Console.WriteLine("Secret is [{0}]", secret); } 

No static state; no issues with threads or multiple tasks. It just works. Note that secret is not just a "local" here; the compiler has worked some voodoo, like it does with iterator blocks and captured variables. Checking reflector, I get:

[CompilerGenerated] private struct <Start>d__0 : IAsyncStateMachine {     // ... lots more here not shown     public string <secret>5__1; } 
like image 21
Marc Gravell Avatar answered Sep 19 '22 15:09

Marc Gravell