TransactionHelper

by Christoph Herold 27. November 2006 11:28

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 HeroldIT.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(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(Resources.ExcNoAmbientTransaction);

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

Tags: ,

Development

Month List

Impressum (for the Germans)

Christoph Herold

Dieses Weblog wird bereitgestellt und verwaltet durch

Christoph Herold
Ignaz-Semmelweis-Str. 37
41540 Dormagen
Deutschland

Sie erreichen mich unter christoph.herold@coeamyd.net.