【发布时间】:2014-02-15 18:04:44
【问题描述】:
EDITED似乎在使用分布式事务(EnterpriseServicesInteropOption.Full)和持久订阅者时,TransactionScope.Dispose 方法不会等待所有提交完成,但只是将方法调用和 TransactionCompleted 事件生成到后台线程。
这不符合明确说明的文档:
此方法是同步的,并且在事务提交或中止之前一直阻塞。
更糟糕的是,似乎没有办法确定所有提交的处理时间。这是有问题的,因为在控制台应用程序中,主线程可以在 dispose 后退出,这有效地杀死了所有后台线程。分布式事务中的远程参与者永远不会被告知这一点,导致锁保持打开、超时和其他丑陋的事情......
另一个问题是,当创建新的 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
【问题讨论】:
-
你是怎么解决这个问题的?我也有同样的问题,也在使用 IBM MQ。
-
我添加了我实现的代码作为答案。这绝不是一个明确的解决方案,但它是我能找到的最好的解决方案。
标签: c# .net transactions transactionscope distributed-transactions