In a couple of previous entries (http://pluralsight.com/blogs/jimjohn/archive/2005/04/14/7332.aspx, http://pluralsight.com/blogs/jimjohn/archive/2005/05/01/7923.aspx) that I posted some time ago, I described how you can use System.Transactions in worker thread scenarios. These cover cases where a master thread wants to farm out work to one or more additional threads, but control will still eventually devolve back to the master thread.
This is a common case, but what about scenarios where there isn't a master thread, but instead a sequence of threads that proceed through events one at a time, eventually completing in some as yet unstarted thread of activity? Well, there is a pattern that can be used for that.
Warning: first a warning about these scenarios: it is generally considered bad practice to have a transaction that spans events that are not tightly constrained to occur 'quickly'. For instance, you wouldn't want to have a transaction span waiting for user input -- either direct text input or some windows form event. Allow transactions to be active across unbounded waiting means that important data may be locked for longer than you are willing to accept, or that some other part of the transaction system may time out the transaction (as a side effect of attempting to ensure that data does not remain locked for too long).
However, there are cases where this pattern can make sense. For instance, if you are using the asynchronous completion pattern, where you know with reasonable certainty that the event will occur in the near future, then this pattern can be used. It just needs to be used with care.
Let's start with a non-transacted pattern that uses IAsyncResult and delegates to chain events together. It is roughly going to have a pattern of:
void DoAsyncOperation ()
{
...
// set up context as an object that contains the execution context
// to run at completion.
... context = ...;
// Now begin the operation
o.BeginInvoke (..., DelegateCompletion, ... context);
// Now do a few more things before returning
...
}
... DelegateCompletion (IAsyncResult ar)
{
... context = (...) ar.AsyncState;
// We now have our context back, and can continue the work,
// including the EndInvoke call
...
}
(Note that I said this was a pattern, rather than fully formed code :) )
Now, what if I wanted to begin the operation in a transaction, and have that transaction continue in the completion delegate, and commit there? To do that, I'd adjust the pattern as so:
void DoAsyncOperation ()
{
...
// set up context as an object that contains the execution context
// to run at completion.
... context = ...;
// Create our new transaction, activate it, and place it into the
// context that will be passed to the completion delegate.
CommittableTransaction tx = new CommittableTransaction ();
context.Transaction = tx;
// Note that we use a DependentTransaction here as the delegate
// can be invoked any time after the BeginInvoke is called.
Transaction dtx = tx.DependentClone
(DependentCloneOption.BlockCommitUntilComplete);
using (TransactionScope scope = new TransactionScope (dtx))
{
// Now begin the operation
o.BeginInvoke (..., DelegateCompletion, ... context);
// Now do a few more things before returning
...
scope.Complete ();
}
dtx.Complete ();
}
... DelegateCompletion (IAsyncResult ar)
{
// Pick out the context, and re-activate the transaction
... context = (…) ar.AsyncState;
CommittableTransaction tx = context.Transaction;
using (TransactionScope scope = new TransactionScope (tx))
{
// We now have our context back, and can continue the work,
// including the EndInvoke call
...
scope.Complete ();
}
// Finally, attempt to commit the transaction
tx.Commit ();
}
This pattern starts a transaction to encompass the operations that occur when starting the async operation. It then passes the commit control to the completion delegate, which is then responsible for re-activating it via TransactionScope and finally committing it. Along the way, we use a DependentTransaction to make sure that a quick completion can't overrun the operations that occur after the BeginInvoke. Any number of completion steps can be chained together this way.
Posted
Jan 01 2006, 09:53 AM
by
jim-johnson