Wednesday 12 December 2012

Understanding SynchronizationContext in ASP.NET

Understanding the SynchronizationContext in ASP.NET.


The SynchronizationContext is a weird animal, and it took me a little while to understand it properly (at least I am under the impression that I do). There are many posts about the SynchronizationContext, and its use thereof, good references are:


Now, what is the SynchronizationContext useful for in ASP.NET?

Looking under the hood with dotPeek (who uses Reflector these days), we can see that there are in fact two contexts used (at least at the time of writing): LegacyAspNetSynchronizationContext and AspNetSynchronizationContext. Both of these are internal classes, exposed through the SynchronizationContext.Current property. Note that the latter is internally in the Microsoft code referred to as the "task friendly" one. You can enable it by setting the following appSettings property in your web.config:

<appSettings>
   <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>

Do not simply adjust this setting as it can lead to deadlocks! Where your code worked before, it may not work after - see below for a discussion.

Both these classes do roughly the same, although one is "friendlier" for the new task based async stuff in the latest versions of .NET. They are important because
  • they ensure the correct user is set on the thread (important in a web site)
  • they make HttpContext.Current available on the new thread
  • they do other stuff I'm not even aware of

As an aside on HttpContext.Current, it is stored in something called the "CallContext", which uses the "IllogicalCallContext" to store the property. I'm not sure what's so "illogical" about this context other than that it's not the "logical" call context, but the important thing to note is that this context does not "flow" to new threads. By default (unless suppressed), ExecutionContext flows from thread to thread, ensuring things like security is set correctly on new threads etc. Now the logical context flows, however the illogical one does not. So HttpContext does not flow as part of the ExecutionContext. If I have to guess I think this is a good thing because HttpContext is not thread safe (no it really isn't). If it would flow I could create some new thread and the context would simply flow to it, but now I'll access it in a thread unsafe manner. As a word of warning: note that SynchronizationContext does in general flow as part of the ExecutionContext, it is just that .NET internally often does *not* flow the SynchronizationContext. These methods are internal and thus we cannot avoid such "flowing" ourselves.

So why is access not thread unsafe whenever I use the SynchronizationContext? Whenever an asynchronous  operation completes it is supposed to post the AsyncCallback to the SynchronizationContext, this post operation does the following:

  • enter a lock (i.e. only one operation is completed at a time)
  • reinstate HttpContext.Current, the current User and some other properties
  • execute the callback under this new context

Note that the newer ASP.NET SynchronizationContext does not execute under a lock as such, but posts the callbacks in a chain of Task continuations. I.e. post A creates a Task such that when post B happens it becomes a continuation on post A, such that they execute one after the other in order and not at the same time.
As I mentioned above using this new context can lead to deadlocks where none were previously present. For example if ASP.NET is executing a step of the pipeline it queues a task to the context that only completes when the step completes. This means that any asynchronous tasks that post to the synchronization context won't execute until this synchronous step is complete. This in turn implies that I cannot wait for a Task to complete if it uses the new synchronization context, whereas before this worked just fine. As far as I can tell there is no solution to this problem other than to use the old context (please leave a comment if there are actual ways of achieving this).

So, for me this means:
  • if HttpContext.Current returns null somewhere in your ASP.NET code, you have not been using the SynchronizationContext correctly
  • as such "singleton" objects are best stored in HttpContext.Current.Items, see Cup(of T).
  • no need to worry about thread safety of HttpContext because the callbacks always execute in order (never at the same time)
You may wonder if all these callbacks execute one at a time, doesn't this defeat the whole asynchronisity etc?
ASP.NET provides each request with a thread (note that we won't necessarily stay on the same thread), this is the thread we want to do our computationally bound work on in almost all cases. The callbacks we are talking about here are "computational", and so we want them to execute on the single thread for this request. The reason to go asynchronous is because we want to "yield" our thread to an IO operation, in other words as soon as our thread is waiting for IO it may as well return to the threadpool to do other useful work until that IO is completed. This way we can scale better because we don't have threads blocking for IO operations (such as reading files, contacting web services etc. etc.).

3 comments:

  1. It is really a great work and the way in which u r sharing the knowledge is excellent.
    Thanks for helping me to understand basic concepts. As a beginner in Dot Net programming your post help me a lot.Thanks for your informative article. dot net training in velachery | dot net training in chennai

    ReplyDelete
  2. Great post! I am see the great contents and step by step read really nice information.I am gather this concepts and more information. It's helpful for me my friend. Also great blog here with all of the valuable information you have.
    Dot Net Training in Chennai

    ReplyDelete