Results 1 to 10 of 10

Thread: CallContext vs. ThreadStatic vs. HttpContext

  1. #1
    Join Date
    Jan 2006
    Location
    Cambridge, UK
    Posts
    1,340

    Default CallContext vs. ThreadStatic vs. HttpContext

    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/t...xt-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:

    Code:
    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/de...ce07222003.asp

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


    br,
    Erich
    Attached Files Attached Files

  2. #2
    Mark Pollack is offline Spring.NET Co-Lead Spring TeamSpring User
    Join Date
    Sep 2004
    Location
    New York, NY
    Posts
    1,683

    Default

    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

  3. #3
    Join Date
    Jan 2006
    Location
    Cambridge, UK
    Posts
    1,340

    Default

    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:

    Code:
    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

  4. #4
    Join Date
    Jan 2006
    Location
    Cambridge, UK
    Posts
    1,340

    Default

    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:

    Code:
    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);
    		}			
    	}
    }

  5. #5
    Mark Pollack is offline Spring.NET Co-Lead Spring TeamSpring User
    Join Date
    Sep 2004
    Location
    New York, NY
    Posts
    1,683

    Default

    Just a note, since I've linked the docs to this forum page - this is the implementation used.
    Mark

  6. #6
    Join Date
    Jul 2007
    Posts
    1

    Default

    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

  7. #7
    Mark Pollack is offline Spring.NET Co-Lead Spring TeamSpring User
    Join Date
    Sep 2004
    Location
    New York, NY
    Posts
    1,683

    Default

    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/...evelopers.aspx

    Mark

  8. #8
    Join Date
    Aug 2010
    Posts
    1

  9. #9

    Default

    it is really nice information thanks

  10. #10
    Join Date
    Sep 2012
    Posts
    1

    Default

    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;
    }
    }

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •