Although I haven’t used this feature in my regular work, I’ve known for a while that WF4.0 ships with a palette of activities designed to support distributed transactions. A distributed transaction is when you have a transaction, which is an atomic commit of a set of operations with a consistency guarantee, implemented in a multi-party scenario, so that all parties know the transaction either completes or fails atomically. This idea has some obvious implications in banking and e-commerce applications.
In WF4, more specifically all of these transactional activities are implemented based on Microsoft’s .Net DTC model, using the features of System.Transactions namespace.
And we’re going to get even one step more specific. There are two programming models offered by the System.Transactions namespace, an explicit model based on the Transaction class, and an implicit model based on the System.Transactions.TransactionScope class. And WF4 models transactions using an implicit model based on the System.Transactions.TransactionScope concept.
Link - Here is the MSDN overview of Transactions in WF4. To try to save you switching web pages, I’ll reproduce some selected text here:
“WF provides support for participating in System.Transactions transactions by using the TransactionScope activity to scope a transacted unit of work. While the System.Transactions.TransactionScope must be explicitly completed the System.Activities.Statements.TransactionScope activity implicitly calls complete on the transaction upon successful completion. Any activities that are contained in the Body of the TransactionScope activity participate in the transaction.”
This text is trying to draw an analogy between System.Activities.Statements.TransactionScope and System.Transactions.TransactionScope. Since the two classes have the same name and long qualifying namespaces, let’s give them some nicknames, SAS-TransactionScope and ST-TransactionScope. The analogy doesn’t really stand on its own without knowing what a canonical ST-TransactionScope sample app would look like, so... what would it look like?
From ‘Implementing an implicit transaction using TransactionScope (MSDN)’:
Uh. WHAT? This sample is still useless! Because we don’t know what is legitimate transactional work! Is ‘i++’, which increments a variable transactional work? Probably not… so what can actually go inside here?
Well, it turns out that one example of ‘transactional work’ is integrated support for System.Transactions in ADO.Net. And there we will find a real sample showing what using TransactionScope looks like (MSDN Sample:
)
By the way, it also turns out that the way ADO.Net does this, and also the way that you can define your own custom transactional work if you want to, is by implementing a (System.Transactions) ResourceManager. What does a ResourceManager basically do? A ResourceManager basically handles all the nitty-gritty implementation details of making your custom action really transactional, like making sure your custom transactional work can engage in a two-phase commit. Let’s stop going deeper there for now.
Returning to that quote explaining SAS-TransactionScope, things should now be starting to make sense, but to check, here is my own statement of what SAS-Transaction scope does:
“SAS-TransactionScope sets up a ST-TransactionScope on the current logical workflow thread so that when you execute any other activity, such as a custom CodeActivity, System.Transactions.TransactionScope.Current can be resolved. And therefore calls to TransactionScope aware frameworks can work inside Code Activities, just as if they are written inside of regular C# TransactionScope statements.”
This bit now deserves its own section. The WF4 threading model is a little interesting, and there are some good reasons.
Reason # 1: Workflows should be able to be resumed on a different thread than the one they were started on. In fact, it can be a different thread, in a different process than the one it was started on. This is to enable the core features of WF4 - persistence, long-runningness.
Reason # 2: Workflows doing asynchronous work should be able to share OS threads efficiently. In fact, you should be able to have thousands of e.g. AsyncCodeActivities, all in mid-execution using a single OS thread. This parsimonious use of OS thread resources is a decision enabling good performance and scalability characteristics.
What this means is that workflows need to know how to share OS threads with each other without conflicting on their own notion of thread-local state. And actually, thread-local state crops up all the time in the .Net framework! And System.Transactions.TransactionScope.Current is one of those times – it is actually a thread-static property!
So… how does one TransactionScope activity avoid conflicting with other TransactionScope activities that are doing asynchronous work on the same OS thread? The answer is Execution Properties, and especially an execution property of type RuntimeTransactionHandle which executes IExecutionProperty.
The basic flow is
1) TransactionScope creates the RuntimeTransactionHandle and sets it as an execution property 2) the workflow runtime continues running scheduled child activities 3) at some point, the workflow becomes idle waiting for asynchronous work to complete or bookmarks to resume 4) the workflow runtime calls IExecutionProperty.CleanupWorkflowThread() on all active execution properties including the RuntimeTransactionHandle 5) the workflow runtime continues running other workflow instances 6) some workflow runtime (could be a new one, could be the same one as before) reloads our workflow, and calls IExecutionProperty.SetupWorkflowThread() on all its execution properties including the RuntimeTransactionHandle, which restores current thread’s ST-TransactionScope.Current. 7) child activities thereby continue to run in the original ST-TransationScope
You can use ST-TransactionScope.Current inside AsyncCodeActivity BeginExecute() and EndExecute(). However, any asynchronous work you schedule is going to run on a different .Net thread than the main workflow dispatch thread (which is the thread calling BeginExecute()/EndExecute()), and you can’t automatically use ST-TransactionScope.Current from that other thread*.
You should, however, be able to capture the value of ST-TransactionScope.Current during BeginExecute(), and use that captured value to update ST-TransactionScope.Current, or flow the transaction across to another thread while you are doing work on that other thread. (It’s also good to be tidy and unflow it when you are done - you wouldn’t want random code executing on the transaction scope.)
Basic example problem scenario (Disclaimer! This is untested and ‘how-not-to-do-it’ code. Do not use any part of this code as basis for anything. You have been hereby been warned.)
(*you can of course, also encounter this issue if you try to use Task<TResult> or any other off-thread operation inside a regular CodeActivity too. I mention this just in case.)