PDA

View Full Version : CallContext vs. ThreadStatic vs. HttpContext



Erich Eichinger
06-28-2006, 10:09 PM
Hi all,

Since usage of [ThreadStatic], CallContext and HttpContext in Webapplications has already been discussed several times but imho to little, I would like to start an explicit thread on this topic. I think it's vital for avoiding unexpected problems.


I knew it is possible in ASP.Net for a Request to start on thread A and finish on thread B. This is called "thread-agility" by the NET-Team. Therefore I've always been convinced, that choosing CallContext would be save for storing e.g. a NHibernate-Session at the beginning of a Request for use during the rest of the Request ("OpenSessionInView").

To make a long story short: I was completely wrong - the only safe store is HttpContext.Items!

Here is a real good article on this:
http://piers7.blogspot.com/2005/11/threadstatic-callcontext-and_02.html


I went a little deeper to find the reasons why. Here's what I found out:

Any Request starts processing by the next available IO-Thread of the ASP-Workerprocess. All Events during an ASP.NET Request (BeginRequest, AuthenticateRequest, etc.) are executed asynchronously by default. But normally each event completes synchronously, thus the Request continues executing on the same thread.
If any of these events doesn't complete synchronously, the corresponding WaitCallback queues executing of the next eventto the NET ThreadPool. The rest of the Request executes on this ThreadPool-Thread!

I've never understood, what the docs were talking about IO and Worker-Threads. Now I do. In Pseudocode deep inside of HttpApplication you'll find something like this:



void ProcessCurrentEvent()
{
...
EventHandler eh = GetCurrentEventHandler()
IAsyncResult ar = eh.BeginExecute( this, WaitCallback, ... )
if ( ar.CompletedSynchronously )
{
ContinueWithNextEvent()
}
}

void WaitCallback( IAsyncResult ar )
{
WaitForCompletion( ar )
ThreadPool.QueueUserWorkItem( ContinueWithNextEvent );
}

void ContinueWithNextEvent()
{
// Set HttpContext.Current
CallContext.SetData("HtCt") = this.Context;
AdvanceToNextEvent()
ProcessCurrentEvent()
}




I hope I could work out the crucial points with this pseudocode. In the worst case, each event may execute on a different thread. The only thing that stays is HttpContext because it's set on the callcontext each time at the beginning of an event.


The big Problem: This affects nearly everything I've seen from Spring's code so far - and all other libaries I'm using, including most code I've written myself ...


I've attached a sample-web illustrating this (you must reference log4net for compiling). Just call /testasynccalls.aspx and hit F5 several times. You'll most likely see that the Thread-ID set during page-ctor is different from the Thread-ID during rendering the page. Even worse: Although the Thread-ID sometimes is the same, you'll see the value from CallContext has disappeared. That's because the previous thread has been returned to the ThreadPool and by fluke the same Thread is reobtained from the ThreadPool. But the ThreadPool clears a thread's CallContext before using it.

How does the sample work:
During Page-Construction 3 WebService calls are scheduled to be executed during the PreRequestHandlerExecute-Event. These Webservice calls are executed asynchronously to improve performance. Since the calls don't complete synchronously, the following ExecuteHandler-Event already executes on a ThreadPool-Thread.

By the way: This sample is the outcome of my experiments for simplifying performance-optimal calls to WebServices from ASPX-Pages inspired by this article:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnservice/html/service07222003.asp

I think, it's a real cool feature ;-)


br,
Erich

Mark Pollack
06-29-2006, 09:15 PM
Hi,
Great stuff, this has always been circling around in the back of my mind. Does this sound like a reasonable approach - introduce a ContextStore abstraction dependent on application context, i.e. web or 'normal'? If it is a web application then it would return an impl based on HttpContext.Items otherwise it would use CallContext.
Cheers,
Mark

Erich Eichinger
06-29-2006, 09:32 PM
I just wanted to post my ideas to the mailinglist ;-)

Basically I'm thinking the same direction. Taking ideas from the NET ConfigurationSystem and Castle's ActiveRecord Implementation, I would to this:



class LogicalThreadContext
{
static ILogicalThreadContext s_instance;

static public void SetData( string key, object data )
{
s_instance.SetData( key, data );
}

static public object GetData( string key )
{
return s_instance.GetData( key );
}
}


where s_instance is set to the appropriate callcontext or httpcontext implementation.

This rise just one question to me: When should this implementation be set in a Webapplication? imho this requires an additional HttpModule to be sure, the instance is set before any calls to the LogicalThreadContext occur.


br,
Erich

Erich Eichinger
07-26-2006, 10:39 AM
here's the next problem: when handling e.g. the Session_OnEnd event in case of a Session-Timeout, there's no HttpContext available. Thus an implementation of "ContextStore", that purely only uses CallContext or HttpContext will fail in this case. Maybe it's better to simply use the implementation below:



public sealed class WebSafeCallContext
{
private WebSafeCallContext()
{
throw new NotSupportedException("must not be instantiated");
}

public static object GetData( string name )
{
HttpContext ctx = HttpContext.Current;
if (ctx == null)
{
return CallContext.GetData(name);
}
else
{
return ctx.Items[name];
}
}

public static void SetData( string name, object value )
{
HttpContext ctx = HttpContext.Current;
if (ctx == null)
{
CallContext.SetData(name, value);
}
else
{
ctx.Items[name] = value;
}
}

public static void FreeNamedDataSlot( string name )
{
HttpContext ctx = HttpContext.Current;
if (ctx == null)
{
CallContext.FreeNamedDataSlot(name);
}
else
{
ctx.Items.Remove(name);
}
}
}

Mark Pollack
11-04-2006, 04:36 PM
Just a note, since I've linked the docs (http://www.springframework.net/doc-latest/reference/html/threading.html)to this forum page - this is the implementation used.
Mark

jskeet
07-02-2007, 02:00 PM
I've just been investigating this a bit further to see whether the numerous pages suggesting that Thread.CurrentCulture/CurrentUICulture can be set in global.asax are correct or not.

As per an earlier post, I wrote a "fast" page (just logging) and a "slow" page (Thread.Sleep(10000) in Page_Load, plus logging). I set Thread.CurrentCulture to a random culture in global.asax (and logged it), then logged the culture in each of the pages' Page_Load methods.

Results:

1) Thread.CurrentCulture is always preserved as far as I can tell, even when the thread itself changes. In other words, the culture is propagated to the "new" thread.
2) A web application created with VS2003 will demonstrate the behaviour documented in this thread - i.e. thread agility - whether the application is set to use ASP 1.1 or 2.0 in the IIS configuration.
3) A web application created with VS2005 won't demonstrate this behaviour *using this form of testing*. That's not to say that thread agility has gone away - just that it can't been provoked in exactly the same way.
4) An interesting (and alarming point) was shown up by the previous logs - the fast request doesn't complete until the slow request does when in the "thread agile" situation. In other words, the log is:

Start slow
Start fast
End slow
End fast

Yikes! Doesn't sound terribly scalable, does it?

When created with VS2005, however, the log goes:
Start slow
Start fast
End fast
End slow

which is much saner.

A lot of this could be explained by (say) ASP.NET 2.0 keeping a minimum number of "warm" threads from start-up. I don't know enough details to comment further - just reporting what I've seen.

Hopefully this will be useful to someone, if only in terms of the culture being preserved (which was my main reason for investigating).

Jon

Mark Pollack
06-22-2010, 03:10 PM
Not clear what you are asking here? NUnit is not running in a web environment so HttpContext.Current.Server.MapPath() woudl not work. Unit testing ASP.NET apps is quite tricky. You can alleviate much of the pain by having your event handler contain as little code as possible, delegating out to a service layer which you can unit test.

You might want to try Selenium, see http://www.iamnotmyself.com/2009/06/10/GettingStartedWithSeleniumForASPNETDevelopers.aspx

Mark

marvin756
08-14-2010, 02:46 AM
Thanks for the info. :)

seo professional (http://seo-expert-services.one10.info) - seo expert (http://seo-expert-services.one10.info) - seo specialist (http://seo-expert-services.one10.info)

nickseoco
08-04-2012, 09:22 PM
it is really nice information thanks

henrymax
09-07-2012, 06:41 AM
public class Context
{
[ThreadStatic()]
private static Context _Context = null;

private HttpContext _HttpContext = null;

public Context()
{
_HttpContext = HttpContext.Current;
}

public static Context Current
{
if(_Context == null ||
_HttpContext != _HttpContext.Current)
{
_Context = new Context();
}
return _Context;
}
}