Reputation: 8756
According to this MSDN article, you should be able to multithread a process with each thread enlisted in a single root transaction.
I created a sample based on that article where I expect atransaction to be rolled-back (bool[] results
should be all false
in the foreach
loop). Unfortunately, this is not the case, and the outcome is predictably unpredictable (run the example enough times and you will see any combination of bool
values in the array).
In addition, I've tried both DependentCloneOption.BlockCommitUntilComplete
and DependentCloneOption.RollbackIfNotComplete
neither of which produce the expected result.
Secondly, I think ThreadPool.QueueUserWorkItem
is ugly code at best, and it would be nice to see something like this using Parallel.ForEach
instead.
And finally, my question :) Why the heck is this not working? What am I doing wrong? Is it just flat-out impossible to wrap multiple threads in a single transaction?
namespace Playing
{
class Program
{
static bool[] results = new bool[] { false, false, false };
static void Main(string[] args)
{
try
{
using (var outer = new TransactionScope(
TransactionScopeOption.Required))
{
for (var i = 0; i < 3; i++ )
{
ThreadPool.QueueUserWorkItem(WorkerItem,
new Tuple<int, object>(
i, Transaction.Current.DependentClone(
DependentCloneOption.BlockCommitUntilComplete)));
}
outer.Complete();
}
}
catch { /* Suppress exceptions */ }
// Expect all to be false
foreach (var r in results)
Console.WriteLine(r);
}
private static void WorkerItem(object state)
{
var tup = (Tuple<int, object>)state;
var i = tup.Item1;
var dependent = (DependentTransaction)tup.Item2;
using (var inner = new TransactionScope(dependent))
{
// Intentionally throw exception to force roll-back
if (i == 2)
throw new Exception();
results[i] = true;
inner.Complete();
}
dependent.Complete();
}
}
}
Upvotes: 4
Views: 1578
Reputation: 29796
Yours results[] members that have been set to true won't magically set themselves back to false (sadly). That's what Transaction Managers do. Look at the EnlistXXX methods to get an idea of what's involved.
Basically, you'll need to compensate in the event of a rollback. For example, you could subscribe to the root Transaction's TransactionCompleted event and check if the transaction was rolled back. If it was you'll need to restore the previous values for the child workers that completed.
You can also handle the TransactionAbortedException thrown that you are suppressing, or handle it at the worker level (see an example of catching it on this page: http://msdn.microsoft.com/en-us/library/ms973865.aspx)
Typically, with in-memory "transactions" you are better off using the Task library to have the workers batch up results and then "commit" them in a continuation of a parent Task. It's easier than messing about with Transactions, which you only need to do if you are coordinating between memory and some other Transaction Manager (like SQL Server or other processes).
Upvotes: 4