Overclock.net › Forums › Software, Programming and Coding › Coding and Programming › Application Programming › Simple thread transactions in C#
New Posts  All Forums:Forum Nav:

Simple thread transactions in C#

post #1 of 3
Thread Starter 
Today, in this post, I am going to show you a concept that I have written for a few of my projects: Simple thread transactions.

I know what you are already thinking - yet another up in the sky thread and what the hell is a simple thread transaction?

I'd say that most of us are familiar with the concept of a transaction: A batch of operations that are atomic. They either all complete, or they all fail. Now, Windows already has transactional APIs, but they are not for the faint of heart and so I am going to release some of my code out into the wild that will provide a limited transactional functionality that is lightweight.

I will include the code (a simple C# class) and some usage scenarios. You can have one transaction operating per thread in your program.

The Code Warning: Spoiler! (Click to show)
Code:
/// <summary>
/// Transaction object used for batching up operations with a simple commit. There can only be one transaction
/// at a time, per thread. Support for atomic operations via Abort() and AbortWithException().
/// </summary>
internal sealed class CoreFastTransaction : IDisposable
{
        [ThreadStatic]
        private static CoreFastTransaction _currentThreadTransaction;

        private bool _isCompleted = false;
        private bool _isAborted   = false;

        private Exception _lastException;
        private readonly ThreadLocal<int> _createdThreadId = new ThreadLocal<int>();

        /// <summary>
        /// Fired when the transaction is successfully completed.
        /// </summary>
        internal event EventHandler OnCompleted;

        /// <summary>
        /// Gets the current transaction for the current thread.
        /// </summary>
        internal static CoreFastTransaction Current
        {
                get
                {
                        return _currentThreadTransaction;
                }
        }

        /// <summary>
        /// Gets whether the current thread has an in-progress transaction.
        /// </summary>
        internal static bool ThreadHasTransaction
        {
                get
                {
                        return ( Current != null );
                }
        }

        /// <summary>
        /// Gets the last exception that occurred for this transaction.
        /// </summary>
        internal Exception LastException
        {
                get
                {
                        return this._lastException;
                }
        }

        /// <summary>
        /// Gets whether this transaction is complete.
        /// </summary>
        internal bool IsCompleted
        {
                get
                {
                        return this._isCompleted;
                }
        }

        /// <summary>
        /// Gets whether this transaction is aborted (IsCompleted is also true).
        /// </summary>
        internal bool IsAborted
        {
                get
                {
                        return this._isAborted;
                }
        }

        /// <summary>
        /// 
        /// </summary>
        private CoreFastTransaction()
        { }

        /// <summary>
        /// Factory
        /// </summary>
        /// <returns></returns>
        internal static CoreFastTransaction Create()
        {
                if ( _currentThreadTransaction != null && !_currentThreadTransaction.IsCompleted )
                {
                        throw new InvalidOperationException( "Complete the transaction on the current thread before starting a new one." );
                }

                _currentThreadTransaction = new CoreFastTransaction();
                _currentThreadTransaction._createdThreadId.Value = Thread.CurrentThread.ManagedThreadId;

                return _currentThreadTransaction;
        }

        /// <summary>
        /// Aborts the transaction, supplying an exception.
        /// </summary>
        /// <param name="exception"></param>
        internal void AbortWithException( Exception exception )
        {
                this._VerifyThreadConcurrency();

                if ( exception != null )
                {
                        this._lastException = exception;
                        this.Abort();
                }
        }

        /// <summary>
        /// Aborts the transaction.
        /// </summary>
        internal void Abort()
        {
                this._VerifyThreadConcurrency();

                this._isAborted = true;
                this._isCompleted = true;
        }

        /// <summary>
        /// Completes the transaction (success).
        /// </summary>
        internal void Complete()
        {
                this._VerifyThreadConcurrency();

                if ( !this._isAborted && !this._isCompleted )
                {
                        if ( this.OnCompleted != null )
                        {
                                this.OnCompleted( this, EventArgs.Empty );
                        }

                        this._isCompleted = true;
                }
        }

        /// <summary>
        /// 
        /// </summary>
        private void _VerifyThreadConcurrency()
        {
                if ( !this._createdThreadId.IsValueCreated ||
                        this._createdThreadId.Value != Thread.CurrentThread.ManagedThreadId )
                {
                        throw new InvalidOperationException( "Cannot perform transaction operation on a different thread than it was created on." );
                }
        }

        /// <summary>
        /// 
        /// </summary>
        ~CoreFastTransaction()
        {
                this.Dispose( true );
        }

        #region IDisposable Members
        /// <summary>
        /// 
        /// </summary>
        void IDisposable.Dispose()
        {
                this.Complete();
                this.Dispose( true );

                GC.SuppressFinalize( this );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fDisposing"></param>
        private void Dispose( bool fDisposing )
        {
                if ( fDisposing )
                {
                        if ( this._createdThreadId != null )
                        {
                                this._createdThreadId.Dispose();
                        }
                }
        }
        #endregion
}

Code Usage

This simple transaction class can be used to permit code re-use. Consider an API in your program that deletes objects from a database. You might have two methods: DeleteSingleObject and DeleteMultipleObjects. How can we have the multiple-objects version re-use the error handling and other code from the single-object version? Moreover, how can we make sure that we execute a piece of code only after all objects have been successfully deleted? Transactions to the rescue!

Consider the below method that deletes a single object from a database:
Code:
internal bool DeleteSingleObject( ObjectHandleType object )
{
        if ( object == null )
        {
                throw new ArgumentNullException( "No object to delete was specified.", "object" );
        }

        if ( !ObjectExists( object ) )  /* ObjectExists() is implemented somewhere else, and checks if the object exists */
        {
                throw new Exception( "The object to delete does not exist." ):
        }

        if ( !ObjectDelete( object ) ) /* ObjectDelete() is implemented somewhere else, and actually does the work of deleting the object */
        {
                throw new Exception( "Failed to delete the object." ); /* Of course ObjectDelete() should return WHY it failed, but this is outside the scope of this thread */
        }

        /* Successfully deleted the object */
        return true;
}

How can we convert this into the multiple-objects version of this API? (i.e. the DeleteMultipleObjects method). We could call DeleteSingleObject in a loop but then we wouldn't easily be able to know what happened if an operation failed. We could try...catch as we use exceptions, however this is bad in a loop (especially tight loops). We know the method returns false on failure, however that isn't enough information. The answer? Wrap the whole thing up in our new transactions!

Consider the below method that deletes multiple objects from a database:
Code:
internal bool DeleteMultipleObjects( params ObjectHandleType[] objects )
{
        /* The "using" block ensures the transaction is completed (aborted or not) */
        using ( CoreFastTransaction transaction = CoreFastTransaction.Create() )
        {
                /* 
                 * Before we start our loop, we can use the OnCompleted event of your transaction to have some action execute when
                 * when the transaction completes successfully. A successful completion is defined by all operations inside the transaction
                 * completing without calling Abort() or AbortWithException() on the current transaction.
                 */
                transaction.OnCompleted += ( sender, e ) => MessageBox.Show( "COMPLETED HOORAY!" );

                for ( int i = 0; i < object.Length; ++i )
                {
                        /* Check for abortion (lolol) */
                        if ( transaction.IsAborted )
                        {
                                /* If an exception was given during the abort */
                                if ( transaction.LastException != null )
                                {
                                        MessageBox.Show( "The following error occurred: " +transaction.LastException.Message );
                                }

                                return false;
                        }

                        DeleteSingleObject( objects[i] );
                }
        } /* Transaction completes here */

        return true;
}

Now we just need to tweak our DeleteSingleObject method to deal with the possibility of transactions (and a helper function):
Code:
internal static void ThrowOrAbortTransaction( Exception result )
{
        /* If in a transaction (and possibly a loop), don't throw an exception! */
        if ( CoreFastTransaction.ThreadHasTransaction )
        {
                CoreFastTransaction.Current.AbortWithException( errorResult );
                return false;
        }

        /* Not in transaction - throw exception */
        throw errorResult;      
}

internal bool DeleteSingleObject( ObjectHandleType object )
{
        /* Check for abortion (lolol) */
        if ( CoreFastTransaction.ThreadHasTransaction && CoreFastTransaction.Current.IsAborted )
        {
                return false;
        }

        Exception errorResult = null;

        if ( object == null )
        {
                errorResult = new ArgumentNullException( "No object to delete was specified.", "object" );
                ThrowOrAbortTransaction( errorResult );

                return false;
        }

        if ( !ObjectExists( object ) )  /* ObjectExists() is implemented somewhere else, and checks if the object exists */
        {
                errorResult = new Exception( "The object to delete does not exist." ):
                ThrowOrAbortTransaction( errorResult );

                return false;
        }

        if ( !ObjectDelete( object ) ) /* ObjectDelete() is implemented somewhere else, and actually does the work of deleting the object */
        {
                 /* Of course ObjectDelete() should specify WHY it failed, but this is outside the scope of this thread */
                errorResult = new Exception( "Failed to delete the object." );
                ThrowOrAbortTransaction( errorResult );

                return false;
        }

        /* Successfully deleted the object */
        return true;
}

I know someone will find this useful smile.gif If you need a lightweight transaction mechanism, this is for you. All code is open source!
Ol' Sandy
(28 items)
 
"Zeus"
(12 items)
 
Elite Preview
(6 items)
 
CPUMotherboardGraphicsRAM
Intel Xeon E3-1230v3 Gigabyte GA-Z97X-UD5H-BK MSI Gaming GTX 980 Kingston 32GB (4x8) 
Hard DriveHard DriveHard DriveHard Drive
Plextor PX-256M5S 256GB Samsung EVO 1TB Hitachi HDS721010CLA332 Hitachi HDS723020BLA642 
Hard DriveHard DriveHard DriveOptical Drive
Hitachi HDS723020BLA642 Hitachi HUA722010CLA330 WDC WD10EARS-00Z5B1 TSSTcorp CDDVDW SH-S223B 
CoolingCoolingOSMonitor
Phanteks PH-TC14PE with TY-140's Lamptron FCv5 (x2) Windows 8 Pro 64-bit Dell U2412M 
MonitorMonitorMonitorKeyboard
Dell U2412M Dell U2212HM Dell U2713HM Topre Realforce 87UB | Ducky DK9087 G2 Pro 
PowerCaseMouseMouse Pad
Corsair AX-750 Corsair Obsidian 650D Logitech G700 XTRAC Ripper XXL 
AudioAudioAudioAudio
Beyerdynamic DT-770 Pro 250ohm Schiit Bifrost DAC Schiit Asgard 2 HiVi Swan M50W 2.1 
CPUMotherboardRAMHard Drive
Intel Xeon E5-2620 Super Micro X9SRL-F-B 128GB 1333MHz LSI 9271-8i 
OSPowerCase
VMware ESXi 5.5 SeaSonic SS-400FL2 Fractal Define R3 
CPUMotherboardGraphicsRAM
Intel Core i5-3437U HP EliteBook Folio 9470m  Intel HD Graphics 4000  16GB DDR3 SDRAM 
Hard DriveOS
256GB SSD Windows 10 Insider Preview 
  hide details  
Reply
Ol' Sandy
(28 items)
 
"Zeus"
(12 items)
 
Elite Preview
(6 items)
 
CPUMotherboardGraphicsRAM
Intel Xeon E3-1230v3 Gigabyte GA-Z97X-UD5H-BK MSI Gaming GTX 980 Kingston 32GB (4x8) 
Hard DriveHard DriveHard DriveHard Drive
Plextor PX-256M5S 256GB Samsung EVO 1TB Hitachi HDS721010CLA332 Hitachi HDS723020BLA642 
Hard DriveHard DriveHard DriveOptical Drive
Hitachi HDS723020BLA642 Hitachi HUA722010CLA330 WDC WD10EARS-00Z5B1 TSSTcorp CDDVDW SH-S223B 
CoolingCoolingOSMonitor
Phanteks PH-TC14PE with TY-140's Lamptron FCv5 (x2) Windows 8 Pro 64-bit Dell U2412M 
MonitorMonitorMonitorKeyboard
Dell U2412M Dell U2212HM Dell U2713HM Topre Realforce 87UB | Ducky DK9087 G2 Pro 
PowerCaseMouseMouse Pad
Corsair AX-750 Corsair Obsidian 650D Logitech G700 XTRAC Ripper XXL 
AudioAudioAudioAudio
Beyerdynamic DT-770 Pro 250ohm Schiit Bifrost DAC Schiit Asgard 2 HiVi Swan M50W 2.1 
CPUMotherboardRAMHard Drive
Intel Xeon E5-2620 Super Micro X9SRL-F-B 128GB 1333MHz LSI 9271-8i 
OSPowerCase
VMware ESXi 5.5 SeaSonic SS-400FL2 Fractal Define R3 
CPUMotherboardGraphicsRAM
Intel Core i5-3437U HP EliteBook Folio 9470m  Intel HD Graphics 4000  16GB DDR3 SDRAM 
Hard DriveOS
256GB SSD Windows 10 Insider Preview 
  hide details  
Reply
post #2 of 3
that is really really good. thank you so much. that was really very helpful.

I'm sorry I just saw this thread today. I don't know how I missed it stun.gif
    
CPUMotherboardGraphicsRAM
Intel 3930K 5040MHz @ 1.48 - 1.53v @ VTT/VCCSA ... Asus rampage extreme IV Gigabyte GTX 970 G1 @ 1580/4050MHz @ 1.325V Corsair Vengeance 64GB (8 x 8GB) DDR3 2400MHz. ... 
Hard DriveHard DriveHard DriveHard Drive
Samsung 850 Pro 256GB Seagate Barracuda ST3000DM001 3TB WD Black 1TB 64Mb Cache  WD Blue 500GB 16mb Cache 
Hard DriveOptical DriveCoolingCooling
WD Black 4TB 64mb Cache LG DVD EK-FB KIT RE4 - Acetal EK-FC970 GTX WF3 Backplate - Black 
CoolingCoolingCoolingCooling
Laing D5 Vario 12V DC Pump (MCP 655)  EK-BAY SPIN Reservoir - Plexi EK-CoolStream RAD XT (240) EK-CoolStream RAD XTX (120) 
CoolingCoolingCoolingCooling
EK-CoolStream XE 120 (Single) 8x Corsair Air SP120 PWM High Performance Edition 3x Corsair Air AF120 High Performance Edition EK-Supremacy EVO CPU Water Block (Nickel Acetal) 
CoolingOSMonitorMonitor
EK-FC970 GTX WF3 - Acetal+Nickel Windows 10 64-bit v1511 build 10586.14 BenQ RL2755HM LG W2261 
MonitorKeyboardPowerCase
Samsung S23C350 Razer BlackWidow Ultimate Cooler Master Silent Pro Gold 1000W Thermaltake Level 10 GT 
Mouse
Razer Lachesis 5600DPI 
  hide details  
Reply
    
CPUMotherboardGraphicsRAM
Intel 3930K 5040MHz @ 1.48 - 1.53v @ VTT/VCCSA ... Asus rampage extreme IV Gigabyte GTX 970 G1 @ 1580/4050MHz @ 1.325V Corsair Vengeance 64GB (8 x 8GB) DDR3 2400MHz. ... 
Hard DriveHard DriveHard DriveHard Drive
Samsung 850 Pro 256GB Seagate Barracuda ST3000DM001 3TB WD Black 1TB 64Mb Cache  WD Blue 500GB 16mb Cache 
Hard DriveOptical DriveCoolingCooling
WD Black 4TB 64mb Cache LG DVD EK-FB KIT RE4 - Acetal EK-FC970 GTX WF3 Backplate - Black 
CoolingCoolingCoolingCooling
Laing D5 Vario 12V DC Pump (MCP 655)  EK-BAY SPIN Reservoir - Plexi EK-CoolStream RAD XT (240) EK-CoolStream RAD XTX (120) 
CoolingCoolingCoolingCooling
EK-CoolStream XE 120 (Single) 8x Corsair Air SP120 PWM High Performance Edition 3x Corsair Air AF120 High Performance Edition EK-Supremacy EVO CPU Water Block (Nickel Acetal) 
CoolingOSMonitorMonitor
EK-FC970 GTX WF3 - Acetal+Nickel Windows 10 64-bit v1511 build 10586.14 BenQ RL2755HM LG W2261 
MonitorKeyboardPowerCase
Samsung S23C350 Razer BlackWidow Ultimate Cooler Master Silent Pro Gold 1000W Thermaltake Level 10 GT 
Mouse
Razer Lachesis 5600DPI 
  hide details  
Reply
post #3 of 3
Thread Starter 
Quote:
Originally Posted by Fantasy View Post

that is really really good. thank you so much. that was really very helpful.
I'm sorry I just saw this thread today. I don't know how I missed it stun.gif

biggrin.gif

I knew someone would be interested tongue.gif
Ol' Sandy
(28 items)
 
"Zeus"
(12 items)
 
Elite Preview
(6 items)
 
CPUMotherboardGraphicsRAM
Intel Xeon E3-1230v3 Gigabyte GA-Z97X-UD5H-BK MSI Gaming GTX 980 Kingston 32GB (4x8) 
Hard DriveHard DriveHard DriveHard Drive
Plextor PX-256M5S 256GB Samsung EVO 1TB Hitachi HDS721010CLA332 Hitachi HDS723020BLA642 
Hard DriveHard DriveHard DriveOptical Drive
Hitachi HDS723020BLA642 Hitachi HUA722010CLA330 WDC WD10EARS-00Z5B1 TSSTcorp CDDVDW SH-S223B 
CoolingCoolingOSMonitor
Phanteks PH-TC14PE with TY-140's Lamptron FCv5 (x2) Windows 8 Pro 64-bit Dell U2412M 
MonitorMonitorMonitorKeyboard
Dell U2412M Dell U2212HM Dell U2713HM Topre Realforce 87UB | Ducky DK9087 G2 Pro 
PowerCaseMouseMouse Pad
Corsair AX-750 Corsair Obsidian 650D Logitech G700 XTRAC Ripper XXL 
AudioAudioAudioAudio
Beyerdynamic DT-770 Pro 250ohm Schiit Bifrost DAC Schiit Asgard 2 HiVi Swan M50W 2.1 
CPUMotherboardRAMHard Drive
Intel Xeon E5-2620 Super Micro X9SRL-F-B 128GB 1333MHz LSI 9271-8i 
OSPowerCase
VMware ESXi 5.5 SeaSonic SS-400FL2 Fractal Define R3 
CPUMotherboardGraphicsRAM
Intel Core i5-3437U HP EliteBook Folio 9470m  Intel HD Graphics 4000  16GB DDR3 SDRAM 
Hard DriveOS
256GB SSD Windows 10 Insider Preview 
  hide details  
Reply
Ol' Sandy
(28 items)
 
"Zeus"
(12 items)
 
Elite Preview
(6 items)
 
CPUMotherboardGraphicsRAM
Intel Xeon E3-1230v3 Gigabyte GA-Z97X-UD5H-BK MSI Gaming GTX 980 Kingston 32GB (4x8) 
Hard DriveHard DriveHard DriveHard Drive
Plextor PX-256M5S 256GB Samsung EVO 1TB Hitachi HDS721010CLA332 Hitachi HDS723020BLA642 
Hard DriveHard DriveHard DriveOptical Drive
Hitachi HDS723020BLA642 Hitachi HUA722010CLA330 WDC WD10EARS-00Z5B1 TSSTcorp CDDVDW SH-S223B 
CoolingCoolingOSMonitor
Phanteks PH-TC14PE with TY-140's Lamptron FCv5 (x2) Windows 8 Pro 64-bit Dell U2412M 
MonitorMonitorMonitorKeyboard
Dell U2412M Dell U2212HM Dell U2713HM Topre Realforce 87UB | Ducky DK9087 G2 Pro 
PowerCaseMouseMouse Pad
Corsair AX-750 Corsair Obsidian 650D Logitech G700 XTRAC Ripper XXL 
AudioAudioAudioAudio
Beyerdynamic DT-770 Pro 250ohm Schiit Bifrost DAC Schiit Asgard 2 HiVi Swan M50W 2.1 
CPUMotherboardRAMHard Drive
Intel Xeon E5-2620 Super Micro X9SRL-F-B 128GB 1333MHz LSI 9271-8i 
OSPowerCase
VMware ESXi 5.5 SeaSonic SS-400FL2 Fractal Define R3 
CPUMotherboardGraphicsRAM
Intel Core i5-3437U HP EliteBook Folio 9470m  Intel HD Graphics 4000  16GB DDR3 SDRAM 
Hard DriveOS
256GB SSD Windows 10 Insider Preview 
  hide details  
Reply
New Posts  All Forums:Forum Nav:
  Return Home
  Back to Forum: Application Programming
Overclock.net › Forums › Software, Programming and Coding › Coding and Programming › Application Programming › Simple thread transactions in C#