如何确定TransactionScope已调用所有提交?

时间:2014-01-24 16:39:51

标签: c# .net transactions transactionscope distributed-transactions

已编辑似乎在使用分布式事务(EnterpriseServicesInteropOption.Full)和持久订阅者时,TransactionScope.Dispose方法不会等待所有提交完成,但只是将方法调用和TransactionCompleted事件生成到后台线程。

这不符合明确说明的文件:

  

此方法是同步的并阻塞,直到事务已提交或中止。

更糟糕的是,似乎没有办法确定何时处理了所有提交。这是有问题的,因为在控制台应用程序中,主线程可以在dispose之后退出,这有效地杀死所有后台线程。分布式事务中的远程参与者将永远不会收到通知,导致锁定仍然打开,超时和其他丑陋的事情......

另一个问题是,当创建一个新的TransactionScope时,参与者仍然可以在旧事务中注册时与旧事务相关联。

下面的(简化)代码演示了这个问题。

我的问题:是否有人知道如何确定是否可以安全地启动新循环?我无法访问Worker的代码,因此我无法更改其中的任何内容... 添加Thread.Sleep(1000)可以解决问题,但这会导致性能下降......

EDITED

internal class TransactionScopeTest
{
    [STAThread]
    public static void Main()
    {
        var transactionOptions = new TransactionOptions { Timeout = TransactionManager.DefaultTimeout };
        var worker = new Worker();
        var transactionCompletedEvent = new AutoResetEvent(true); // true to start a first loop

        while (true)
        {
            transactionCompletedEvent.WaitOne(); // wait for previous transaction to finish

            Log("Before TransactionScope");
            using (var tx = new TransactionScope(TransactionScopeOption.Required, transactionOptions, EnterpriseServicesInteropOption.Full))
            {
                Log("Inside TransactionScope");
                Transaction.Current.TransactionCompleted += delegate
                {
                    transactionCompletedEvent.Set(); // allow a next loop to start
                    Log("TransactionCompleted event");
                };
                worker.DoWork();
                Log("Before commit");
                tx.Complete();
                Log("Before dispose");
            }
            Log("After dispose");
        }
    }

    private static void Log(string message)
    {
        Console.WriteLine("{0} ({1})", message, Thread.CurrentThread.ManagedThreadId);
    }


    public class Worker : IEnlistmentNotification
    {
        private Transaction _transaction;
        private readonly Guid _id = Guid.NewGuid();

        public void Prepare(PreparingEnlistment preparingEnlistment)
        {
            Log("Preparing");
            preparingEnlistment.Prepared();
        }

        public void Commit(Enlistment enlistment)
        {
            Log("Committing");
            _transaction = null;
            enlistment.Done();
        }

        public void Rollback(Enlistment enlistment)
        {
            Log("Rolling back");
            _transaction = null;
            enlistment.Done();
        }

        public void InDoubt(Enlistment enlistment)
        {
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "Doubting");
            _transaction = null;
            enlistment.Done();
        }

        public void DoWork()
        {
            Enlist();
            Log("Doing my thing...");
        }

        private void Enlist()
        {
            if (_transaction == null) //Not yet enlisted
            {
                Log("Enlisting in transaction");
                _transaction = Transaction.Current;
                _transaction.EnlistDurable(_id,this, EnlistmentOptions.EnlistDuringPrepareRequired);
                return;
            }
            if (_transaction == Transaction.Current) //Already enlisted in current transaction
            {
                return;
            }
            throw new InvalidOperationException("Already enlisted in other transaction");
        }
    }
}

输出:

Before commit (1)
Before dispose (1)
Preparing (6)
After dispose (1)
Committing (6)
TransactionCompleted event (7)
Before TransactionScope (1)
Inside TransactionScope (1)
Enlisting in transaction (1)
Doing my thing... (1)
Before commit (1)
Before dispose (1)
Preparing (7)
After dispose (1)
Before TransactionScope (1)
TransactionCompleted event (7)
Inside TransactionScope (1)
Committing (6)

Unhandled Exception: System.InvalidOperationException: Already enlisted in other transaction

2 个答案:

答案 0 :(得分:1)

Transaction.Current.TransactionCompleted始终在worker.Commit通知后执行。添加AutoResetEvent以跟踪TransactionCompleted并在开始新循环之前等待它:

var transactionCompletedEvent = new AutoResetEvent(true); // true to start a first loop

while (true)
{
    transactionCompletedEvent.WaitOne(); // wait for previous transaction to finish

    Log("Before TransactionScope");
    using (var tx = new TransactionScope(TransactionScopeOption.Required, transactionOptions, EnterpriseServicesInteropOption.Full))
    {
        Log("Inside TransactionScope");
        Transaction.Current.TransactionCompleted += delegate 
        {
            transactionCompletedEvent.Set(); // allow a next loop to start
            Log("TransactionCompleted event"); 
        };
        worker.DoWork();
        Log("Before commit");
        tx.Complete();
        Log("Before dispose");
    }
    Log("After dispose");
}

答案 1 :(得分:-1)

或多或少有效的唯一解决方案是在退出控制台应用程序之前等待所有后台线程停止。

我是在退出应用程序之前调用以下代码实现的:

public static class ThreadTools
{ 
    /// <summary>
    /// Wait until all worker threads have finished their job.
    /// </summary>
    public static void WaitForBackgroundThreads()
    { 
        int workerThreads = 0;
        int completionPortThreads = 0;
        int maxWorkerThreads;
        int maxCompletionPortThreads;
        ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxCompletionPortThreads); 
        while(workerThreads != maxWorkerThreads || completionPortThreads != maxCompletionPortThreads)
        { 
            Thread.Sleep(100);
            ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
        }
    }
}

我意识到这只是一个黑客攻击,但在有人给我一个更好的解决方案之前,这是我能想到的最佳答案。