none
ASP.NET Thread Affinity and System.Transaction RRS feed

  • Question

  • So this seems to be a common theme that comes up when using the ThreadStaticAttribute.

    Some articles (such as this) claim you shouldn't use ThreadStaticAttribute in ASP.NET or WCF because the thread may be different by the end of the method call. i.e.

    public MyService : IMyContract
    {
    	public void MyMethod()
    	{
    		DoQuickStuff(); // Thread #1
    		DoLongIOStuff(); // Changes thread here.
    		DoQuickStuff; // Thread #2
    	}
    }
    If this is the case, how does the Thread.Current work in these cases? Looking at the source for System.Transaction, it appears to simply use a ThreadStaticAttribute field, so shouldn't this fail if the transaction spans a long running IO operation? Is Transaction.Current not used by TransactionScope to determine which transaction to commit/rollback?

    Can someone explain to me why Transaction.Current is considered fine for WCF and Web Applications, but ThreadStaticAttribute is not?



    Wednesday, June 12, 2013 3:06 PM

Answers

  • Thread affinity doesn't guarantee which thread will process a result.  I don't know that I completely agree with the article that within a single service call as given that the thread would switch.  One of the fundamental implementation rules that pretty much every language follows is that method calls are handled through the call stack and the stack is per thread.  Moving a function call in the middle of execution would break this rule.  Therefore I cannot imagine the scenario the article mentioning actually ever occurring.  However there are lots of things to take into account before you could say it is or isn't possible. 

    1) Host - The host controls how the service executes and therefore which threads it will use.  Out of the box WCF is going to use the thread pool (or IO threads, cannot remember).

    2) Mode - How the service is configured can also impact things such as how many calls it can process at once.

    3) Blocking calls - await throws a wrinkle into things

    Within a single method call without any awaits I still believe you can guarantee that it is the same thread.  If it wasn't the same thread then I'd be baffled as to how the call stack would be persisted.  .NET does provide functionality for snapshoting the execution context but I don't see how that code could be inserted inside a method that is already compiled.  In general when thread affinity is mentioned you should interpret it as meaning whether there is a guarantee the code will execute on a specific thread or not.  I think you should consider posting the question about a WCF method body being split across threads while in the call to the WCF forums.  Certainly the request can span multiple threads and there is no guarantee which one but the actual method body that you write probably not. 

    Michael Taylor
    http://msmvps.com/blogs/p3net

    • Marked as answer by Ben E D Ellis Wednesday, June 12, 2013 10:12 PM
    Wednesday, June 12, 2013 9:20 PM
    Moderator

All replies

  • The claims aren't that you shouldn't use TSA but rather you should be aware of the behavior.  TSA indicates that a static value can be different across threads.  Normally a static field has the same value across all threads but a TS field can have a different value on each thread.  Under the hood it is stored in the TLS for the thread.  Hence if you retrieve the value on 2 different threads you can get 2 different values.  TS is useful for values that are truly thread-specific (such as the remote IP address of a thread used for client-server work). 

    What the articles are trying to clarify is that ASP.NET, WCF and anything using Task do not guarantee that the thread that started the request will be the same as the thread that finishes it (the thread affinity).  For the simple case of a WCF method that simply returns a value then the thread will be the same but any method that makes an async call does not have that guarantee.  Therefore if you're using TSA and your ASP.NET/WCF call does not have thread affinity then you need to be aware that the TSA value will potentially be different.  Provided you understand that then there is nothing wrong with using TSA (although I'm hard pressed to come up with a scenario where it would make sense to do so).

    In regards to Transaction.Current, it is TS.  It is documented as such.  Furthermore it is documented that if you were to change the value then an exception would be thrown.  Hence Transaction.Current can only be used from the thread that started the ambient transaction. Additionally that would mean that ambient transactions are not really usable in ASP.NET/WCF calls that don't have thread affinity.  To clarify though you can use them inside a single thread but you wouldn't start the transaction at the beginning of the request and commit it at the end.  Instead you'd start the request and then in the background thread you'd start and commit the transaction.

    Michael Taylor
    http://msmvps.com/blogs/p3net

    Wednesday, June 12, 2013 5:18 PM
    Moderator
  • So is the following code snippet potentially unsafe in that either an exception will be thrown because the current transaction cannot be found, or, the wrong transaction would be committed/rolled back?

    public class MyService : IMyServiceContract
    {
        public int DoStuff()
        {
            using (var ts = new TransactionScope())
            {
                DoSomethingInTheTransaction();
    
                // Possibly the thread changes
                DoLongIoOperationOrUseAwait(); 
    
                ts.Complete();
            } /* What happens here if the
               * thread has changed?
               */
        }
    }


    Wednesday, June 12, 2013 7:13 PM
  • Sorry when you say TS do you mean Thread Static or Thread Safe?
    Wednesday, June 12, 2013 7:14 PM
  • Normally when I say TS I mean thread safe but in the contextual discussion of TSA I was referring to thread static.

    The code you posted should be fine because the method call is not async.  Hence the thread that started the method call will be the same thread that finished it.  To be clear the compiler or CLR won't split a method in some arbitrary fashion in order to run it on multiple threads.  A body of a method will run on the same thread irrelevant of what it calls (except as noted below).  So for the life of a method body you can guarantee it is the same thread.

    When we refer to WCF async methods what we're talking about is when a WCF service exposes a method asynchronously such as BeginSomeOp and EndSomeOp.  The client starts the request using BeginSomeOp and then at some point will wait for the results using EndSomeOp.  Behind the scenes the BeginSomeOp call is received by the service on some thread and it is expected to start its processing (on a worker thread).  The service then returns a result that the client can use to get the final results.  When the client calls EndSomeOp later on it'll pass the result object.  The call to EndSomeOp will occur on some thread unrelated to BeginSomeOp.  Hence if BeginSomeOp were to use TSA to store some data then there would be no guarantee that EndSomeOp would have access to that data.

    Now to muddy up the waters a little we have to mention C# 5 and the await keyword.  This keyword doesn't change the runtime rules at all but it does change the compile time rules.  When the await keyword is used inside a method body then the compiler will split the method into separate pieces.  Basically the compiler splits the method into private methods that return tasks such that the caller doesn't block waiting for the results anymore.  As a result you need to be careful when you're using TSA with a method that has the await keyword in it.  Depending upon the sync context being used you might get funny results.  I've never tried using TSA with await so I cannot confirm whether this would work correctly.  In theory it should work just fine but you'd have to try it and see.

    So, in summary, within a single method TSA is fine.  Using TSA across methods that may be run async (such as Begin/End methods in WCF services) should be avoided as they will not work properly.  Your posted code should work just fine as the same thread will run all the body.

    Michael Taylor
    http://msmvps.com/blogs/p3net

    Wednesday, June 12, 2013 8:13 PM
    Moderator
  • Thanks Michael. What you've described is how I've understood the TSA, TLS and the await keyword should work, I'll have a test with the await keyword to see what happens with TLS.

    The item that is confusing me is articles like this one,

    http://blogs.microsoft.co.il/blogs/applisec/archive/2009/11/23/wcf-thread-affinity-and-synchronization.aspx

    that claims that after a call to a long running method the worker thread will (possibly?) be different before and after as (for performance/scalability improvements) ASP.NET/WCF will switch to an IO Thread for the long running operation and return the Worker Thread to the pool for another request, then when the IO operation has completed another worker thread is allocated from the pool.

    Is this claim correct?

    I would be surprised if this were the behaviour unless the await keyword were used or an async operation was being performed as you've described.

    Wednesday, June 12, 2013 9:01 PM
  • Thread affinity doesn't guarantee which thread will process a result.  I don't know that I completely agree with the article that within a single service call as given that the thread would switch.  One of the fundamental implementation rules that pretty much every language follows is that method calls are handled through the call stack and the stack is per thread.  Moving a function call in the middle of execution would break this rule.  Therefore I cannot imagine the scenario the article mentioning actually ever occurring.  However there are lots of things to take into account before you could say it is or isn't possible. 

    1) Host - The host controls how the service executes and therefore which threads it will use.  Out of the box WCF is going to use the thread pool (or IO threads, cannot remember).

    2) Mode - How the service is configured can also impact things such as how many calls it can process at once.

    3) Blocking calls - await throws a wrinkle into things

    Within a single method call without any awaits I still believe you can guarantee that it is the same thread.  If it wasn't the same thread then I'd be baffled as to how the call stack would be persisted.  .NET does provide functionality for snapshoting the execution context but I don't see how that code could be inserted inside a method that is already compiled.  In general when thread affinity is mentioned you should interpret it as meaning whether there is a guarantee the code will execute on a specific thread or not.  I think you should consider posting the question about a WCF method body being split across threads while in the call to the WCF forums.  Certainly the request can span multiple threads and there is no guarantee which one but the actual method body that you write probably not. 

    Michael Taylor
    http://msmvps.com/blogs/p3net

    • Marked as answer by Ben E D Ellis Wednesday, June 12, 2013 10:12 PM
    Wednesday, June 12, 2013 9:20 PM
    Moderator