c# - 如何确定 TransactionScope 调用了所有提交?

标签 c# .net transactions transactionscope distributed-transactions

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

这不符合明确规定的文档:

This method is synchronous and blocks until the transaction has been committed or aborted.

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

另一个问题是,当创建新的 TransactionScope 时,参与者仍然可以与旧交易相关联,而他们应该在新交易中注册。

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

我的问题:是否有人知道如何确定开始新循环是否安全(目前)?我无权访问 Worker 的代码,所以我无法更改其中的任何内容... 添加 Thread.Sleep(1000) 可以解决问题,但这会降低性能...

已编辑

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

最佳答案

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");
}

关于c# - 如何确定 TransactionScope 调用了所有提交?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21337858/

相关文章:

android - 适用于 Android 的单声道 - ExpandableListView

.net - iTextSharp,一款流行的 PDF 生成工具。版本 5.5.11 符合 FIPS 标准。

.NET 远程处理与 SOA

ruby-on-rails - 检查多插入事务是否成功

hibernate 回滚

c# - Xamarin 从 2.5 更新到 3.5 后,Xamarin.Forms.Platform.Android 不存在

c# - 限制可能的字符串参数值并在智能感知中查看它们的正确方法

mysql - MySQL 中的隔离级别更改

c# - 从 C# 调用接受调用者分配的结构数组的 C 函数

c# - 在一个 WPF ComboBox 中使用三种不同的 ComboBoxItem 样式