Multithreading with DependentTransaction

In an earlier article, I explained that you could use the System.Transaction feature DependentTransaction to ensure that a transaction commit was held until all the activity for the transaction had completed.  One scenario that was in our minds was its use in a multithreaded environment.  A commenter asked for an example showing how this could be done.  Below is a simple example:

using System;
using
System.Threading;
using System.Transactions;

namespace MultiThread
{
    class
Program
   
{
       
public static void ThreadProc(object o)
       
{
           
DependentTransaction dtx = (DependentTransaction) o;

           
using (TransactionScope s = new TransactionScope(dtx))
           
{
                // Wait 5 seconds just to make sure this thread
                // completes after the main one.
                //
               
Thread.Sleep(5000);

               
Console.WriteLine("About to complete the worker thread's transaction scope");
               
s.Complete();
            
}

           
Console.WriteLine("Completing the dependent clone");
           
dtx.Complete();
       
}

        static void Main(string[] args)
       
{
           
DependentTransaction dtx;
           
Thread newThread = new Thread (new ParameterizedThreadStart(Program.ThreadProc));

           
using (TransactionScope s = new TransactionScope())
           
{
               
dtx = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
               
newThread.Start(dtx);
               
Console.WriteLine("About to complete the main thread");
               
s.Complete();
           
}

           
Console.WriteLine("Transaction Completed");
        }
   
}
}

When you run it you get the following output:

About to complete the main thread
About to complete the worker thread's transaction scope
Completing the dependent clone
Transaction Completed

What is happening is that a DependentTransaction marked to “block commit until it is complete“ is created in the main thread, and then handed off ot the created thread.  The main thread then immediately begins transaction commitment, which stalls awaiting the completion of the DependentTransaction (if you're really interested in internals, this uses what MSDTC called “Phase 0”).  In the worker thread, the transaction continues, and eventually signals completion of the DependentTransaction.  As soon as that happens, the commit processing started on the original thread wakes up and finishes.


Posted May 01 2005, 07:51 AM by jim-johnson

Comments

Nicholas Paldino [.NET/C# MVP] wrote re: Multithreading with DependentTransaction
on 05-02-2005 6:58 AM
Jim,

Do you find it odd that you have to call complete twice in the thread procedure? Once on the TransactionScope and one on the dependent clone of the transaction? Don't you think that the scope, if taking a dependent transaction in the constructor, should commit this for you? Logically, I can't see why the two would be separated.
Jim Johnson wrote re: Multithreading with DependentTransaction
on 05-03-2005 6:39 AM
Nick,

This is an area that we spent a lot of time discussing. There were a number of scenarios that we wanted to support efficiently, and we wanted to limit the number of different rules that a developer would need to remember. Note that we also considered this a somewhat more advanced feature.

Some of the scenarios that we wanted to support centered around how an application environment would use it. We had in mind a sequence that looked like:

create the transaction
<do some internal work that isn't part of the transaction>
start a transaction scope
<call user application code>
complete the transaction scope

... iterate as necessary

<do internal cleanup>
commit the transaction

There's an equivalent variant for the cases where the transaction is not getting created, but rather is the target for a propagated transaction. In the first case, the environment creates a CommittableTransaction, in the second a DependentTransaction.

To support this, we wanted, as much as possible, for the application environment to be able to use the same pattern for both cases. We also wanted to do so with a minimum of generated garbage (so as few objects as we could). Finally, we want to support this when the iteration involved an internal state machine, and so wasn't as amenable to using a single outer scope, with inner scopes suppressing the transaction during for the internal work.

All these led us to decide that a transaction scope is responsible for controlling the ambient state, but that it does not assume total control of a transaction object passed to it.

Jim.
shreeman wrote re: Multithreading with DependentTransaction
on 05-29-2005 8:01 AM
Hi,

Good intoroduction to a great stuff.However i ve few doubts that is what if the spawn thread is not working on timeout mechanism and it never returns?Is n't it produce contention ?

second how the transaction context get passed between threads?

thx
shreeman
sndshreeman@rediffmail.com


Jim Johnson wrote re: Multithreading with DependentTransaction
on 06-06-2005 7:32 AM
Shreeman,

If I can paraphrase your questions:

- What if the new thread doesn't complete (or for that matter, what if any consumer of DependentClone with BlockCommitUntilComplete doesn't complete)?

Both cases are essentially equivalent to any application that fails to finish its transaction work, and therefore doesn't reach a commit decision point. This should result in the transaction timing out, unless the user has done something specific with their configuration to disable all transaction timeouts.

Put another way, you should think about the BlockCommitUntilComplete feature as helping simplify the application code by allowing it to avoid an exploit resynchronization before intiating commit.

- What does this do for contention?

I don't think it does anything one way or another. The feature simply allows you to more easily express a worker thread pattern. All the behaviors in that pattern remain the same.

- How does the transaction get passed between threads?

In the Main method above, it creates a DependentTransaction, and then simply passes that reference to the worker thread. That transaction then becomes current on the new thread when it is passed into the TransactionScope constructor in the worker method.

Jim.
shreeman wrote re: Multithreading with DependentTransaction
on 06-16-2005 7:14 AM
Hi Jim

thanks for the explanation thus we ve the scope extended to multiple thread and the transaction time out becomes handful here if the spwn thread didn't return and didn't commit until it get the complete signal fromt he spawn thread.
I had got the doubt generated as i din't found any action you ve taken aftre calling new thread that is can't we give the signal back to the main thread here that new thread transaction is complete (any sort of callback...) .In this scenario does n't the main thread wait until the spawn thread complete to commit the transaction?(sync)can we ve a publisher subscriber scenario here too?
upto how many thread can we pass the dependenttransaction object does this not limit to threadpool(though practically that seems not happen)?


Also the dependenttransaction object is part of com+ itself not of msdtc but can we simulate the functionlity of 2 phase commit out msdtc to the framework with any other class?

thx
shreeman
sndshreeman@rediffmail.com
Jim Johnson wrote re: Multithreading with DependentTransaction
on 06-17-2005 7:09 AM
Shreeman,

You're correct that the application code does not include any explicit notifications back to the main thread. That is effectively done from inside DependentTransaction. As a result, dispose of the TransactionScope will wait until the transaction completes -- either because all the DependentTransactions have completed, or because the transaction has timed out.

There's no inherent limit in the number of DependentTransaction instances that you can create. You just need to make sure to have one for each thread that you are synchronizing.


With your last question, I'm not quite sure what you're asking. System.Transactions is not part of COM+, although it is used by System.EnterpriseServices, which is effectively the managed interfaces for COM+. If you're asking if this feature can be used by any managed program, the answer is yes.

If you're asking if DependentTransaction can be built out of the rest of the System.Transactions classes and interfaces, then again, the answer is pretty much yes. It can be largely mimiced through the use of a volatile resource manager that has EnlistDuringPrepareRequired set to true. [There are some failure cases that require some thought to get right, btw]

If you're asking if you can do something like this for unmanaged code, the answer for DependentTransaction is yes, through the use of a Phase0 enlistment. However, System.Transactions has done nothing to simplify unmanaged transactions and the OLE-TX interfaces.

I'm not sure if you're asking any of these questions. If this reply doesn't help, could you please expand on your question?

Thanks,
Jim.
shreeman wrote re: Multithreading with DependentTransaction
on 06-19-2005 6:12 AM
Hi Jim,
Thanks for the NICE explnation and it clear many of my doubts.
Also sometimes typo mistakes also helps in gaining a lot as i forgot to ask can we use dependent transaction out of ES?? and u d replied.

As per my last question is concerned there was alittle typo mistake..that what i was intend to ask was upto the point when we are dealing with a single RM we can enlist any no. of connection into our transaction scope(this what i typed wrong as com+) but when we ve more then one RM the transaction is promoted to msdtc.

My question was do we ve any option system.transaction (though i didn't found yet..)to implement the core msdtc functionality in c#???

Today the last question is how the transaction scope related to the appdomain per db/user session scenario in yukon..

If possible please post how the dependent transaction can be used out of ES .

Thanks
shreeman
Jim Johnson wrote re: Multithreading with DependentTransaction
on 06-27-2005 11:37 AM
Shreeman,

Sorry for the delay in getting back to you.

You asked if DependentTransaction can be used from within System.EnterpriseServices. As far as System.Transactions is concerned, there's no issue. You can just get it by calling Transaction.Current.CreateDependentTransaction.

However, EnterpriseServices does have a strong opinion about user threads, so you'd need to be extremely careful about any threads you create. This isn't an issue with DependentTransaction, but a more general one about a user component fitting in with EnterpriseServices expectations.

You also asked if there's any option for an implementation of the core MSDTC functionality in C#. There are a couple of different ways of interpreting that question.

If I look at System.Transactions from the outside, it has the general capabilities of MSDTC, and, in fact transparently promotes to MSDTC as necessary. From that point of view, System.Transactions does offer the core functionality of MSDTC.

If, however, I look at the internal structure, it is obvious that the full functionality of MSDTC is not fully replicated in the System.Transactions managed code. That is intentional. We made a decision to keep the overall stack with as little overlap as possible, and to have the layers integrate. Our goal has been for there to be one common stack, which applications and resource managers can plug in to -- in other words, we expressly wanted to support managed applications that invoke unmanaged resources or other unmanaged application (i.e. COM+) components. By leveraging the existing MSDTC services, we were able to both achieve this integration and limit the amount of redundant code designed into the stack.

There are a few features that are not available from managed code -- direct XA support being the most obvious. There are also some features in managed code that are not yet available in unmanaged code -- DependentTransactions and lightweight transactions being the most obvious. I would expect that we'd try to resolve these differences over time.

Finally, on the question of per-db/appdomain sessions with Yukon, I'm not quite sure what you're asking. Can you help me understand what you're looking for better?

Thanks,
Jim.
Tecnologie .NET (Dotnet) wrote Transazioni in .NET 2.0
on 09-12-2005 3:55 PM
Florin Lazar's WebLog wrote Phase0 in .Net System.Transactions
on 04-09-2006 3:28 PM
In a previous post (http://blogs.msdn.com/florinlazar/archive/2006/01/29/518956.aspx) I talked about...
Nelis Bijl wrote re: Multithreading with DependentTransaction
on 02-03-2007 12:04 AM
Hi, this message is quite old but still I'd like to try to throw in another question:

In the main thread I open a database session (SQL Server), insert a record, Transaction will commit this when it completes.

The thread opens another database session within the same database connection. This way I can query the uncommited record.

However when the thread opens the database connection after the main thread has called s.Complete() (easily achieved by Thread.Sleep(1000)) I get the following exception:

System.Data.SqlClient.SqlException: Distributed transaction completed. Either enlist this session in a new transaction or the NULL transaction.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlDataReader.ConsumeMetaData()
at System.Data.SqlClient.SqlDataReader.get_MetaData()
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader()
at NHibernate.Impl.BatcherImpl.ExecuteReader(IDbCommand cmd) in D:\devtest\nhibernate-svn\nhibernate\src\NHibernate\Impl\BatcherImpl.cs:line 181
at NHibernate.Loader.Loader.GetResultSet(IDbCommand st, RowSelection selection, ISessionImplementor session) in D:\devtest\nhibernate-svn\nhibernate\src\NHibernate\Loader\Loader.cs:line 1378

Anything I can do to prevent this? I understand that I could let the main thread wait using waitHandles but I thought that was being taken care of by using the DependentTransaction.

Is this a flaw in the DependentTransaction mechanism or in the ADO implementation for SQL Server? Or is it me ;-)
Florin Lazar's WebLog wrote Phase0 in .Net System.Transactions
on 02-11-2007 5:51 AM
In a previous post ( http://blogs.msdn.com/florinlazar/archive/2006/01/29/518956.aspx ) I talked about
Michael S. wrote re: Multithreading with DependentTransaction
on 04-28-2008 4:28 AM
Well I think that is not working.

I made a litte test with two database connections. But very simila to this Example

The Problem is as soon as Complete() is called in the Main TransactionScope every ExecuteNonQuery command gives an "Distributed transaction completed. Either enlist this session in a new transaction or the NULL transaction.

A real Problem is that that eception is not catched by an try/catch in the main funcion. Insted the hole application crashes the bad way.

simplified code:
Main:
try
{
//Open two connections
using(TransactionScope ts = new TransactionScope(TransactionScopeOption.Requred, TimeSpan.MaxValue)
{
ThreadPool.QueueUserWorkItem(worker1, Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete));
ThreadPool.QueueUserWorkItem(worker2, Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete));
ts.Complete();
}
//Close the connections
}
catch(Exception(ex)
{
//Write Exception
}

Worker1:
DepednentTransaction dtx = (DependendTransaction)ar;
using(TrsansactionScope ts = new TransactionScope(dtx))
{
Thread.Sleep(9000);
ts.Complete();
}
dtx.Complete();

Worker2:
DepednentTransaction dtx = (DependendTransaction)ar;
using(TrsansactionScope ts = new TransactionScope(dtx))
{
Thread.Sleep(TimeSpan.FromSeconds(10));
//Make An Insert using one of the connections
ts.Complete();
}
dtx.Complete();
Jim Johnson wrote re: Multithreading with DependentTransaction
on 05-04-2008 6:32 AM
Michael,

Sorry for not replying earlier. It looks like you have taken this to http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3260331&SiteID=1, which is a good place to ask about the current product.

Jim.

Add a Comment

(required)  
(optional)
(required)  
Remember Me?