Powered by: newtelligence dasBlog 1.9.6264.0
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.
E-mail
Theme design by Christoph Herold
For the time being, I have built myself a helper class, that tries to fix the "timeout-bug" when using TransactionScope. And since I'm such a nice guy, I decided to let everyone have it
So here goes, completely commented including a usage example:
using System; using System.Threading; using System.Transactions; namespace Succell.Framework.Utils { /// <summary> /// This class is used to abort transactions when the timeout occurs. /// </summary> /// <remarks> /// <para>This class is required, because TransactionScopes do not abort processing, when they expire. Instead /// all actions done after the timeout occurs are not included in the transaction, but instead carried out /// normally. This can lead to inconsistencies in your data, when processing large amounts.</para> /// /// <para>This class can be used nestedly, i.e. methods called in the supplied working delegate can safely /// create instances of this class.</para> /// </remarks> /// <example><code> /// try /// { /// using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required, new TimeSpan(0, 0, 2))) /// { /// new TransactionHelper(ts, delegate() { /// // do transactive work here /// }); /// ts.Complete(); /// } /// } /// catch (TransactionAbortedException tae) /// { /// // handle aborted transaction /// } /// </code> /// </example> public sealed class TransactionHelper { /// <summary> /// A tagging object to determine, whether the thread is aborted due to a transaction timeout /// </summary> private object _abortObject; /// <summary> /// The current thread executing the transactive work /// </summary> private Thread _currentThread; /// <summary> /// Creates a new TransactionHelper that executes transactive statements and aborts the process /// if a timeout occurs. /// </summary> /// <param name="toExecute">The delegate to call for processing</param> public TransactionHelper(ThreadStart toExecute) { if (null == toExecute) throw new ArgumentNullException("toExecute"); // get current transaction Transaction tx = Transaction.Current; if (null == tx) throw new InvalidOperationException("You must create an ambient transaction before using the TransactionHelper"); // Don't execute, if the transaction is already aborted if (tx.TransactionInformation.Status != TransactionStatus.Aborted) { // keep handle to current thread for aborting _currentThread = Thread.CurrentThread; // register completion handler tx.TransactionCompleted += new TransactionCompletedEventHandler(Current_TransactionCompleted); // execute transactive code try { toExecute(); } catch (ThreadAbortException tae) { // was the thread aborted by our TransactionCompleted handler? if (null != _abortObject && tae.ExceptionState == _abortObject) { Thread.ResetAbort(); } } // unregister completion handler (required when using multiple TxHelpers in one transaction, // otherwise multiple ThreadAbortExceptions are thrown on timeout) tx.TransactionCompleted -= new TransactionCompletedEventHandler(Current_TransactionCompleted); } } /// <summary> /// Transaction completion event handler, that checks, whether a timeout occured /// and, if so, causes the registered executing thread to abort /// </summary> /// <param name="sender">The originating transaction</param> /// <param name="e">The event args for this event</param> void Current_TransactionCompleted(object sender, TransactionEventArgs e) { // Is the transaction aborted (possibly due to timeout)? if (e.Transaction.TransactionInformation.Status == TransactionStatus.Aborted) { // Is the executing thread registered? if (null != _currentThread) { // generate a new abortion tagging object _abortObject = new object(); // abort the executing thread _currentThread.Abort(_abortObject); } } } } }
I hope, this helps some of you. Of course, I'll be updating my blog, as soon as some notice of how TransactionScope is supposed to be used reaches me. I know, that ThreadAbortExceptions aren't something to use lightly, but currently I see no other way.