3

EDITED看來,使用分佈式事務(EnterpriseServicesInteropOption.Full)和長期訂戶時,該TransactionScope.Dispose方法不會等待所有提交已經完成,只是滋生的方法調用和TransactionCompleted事件後臺線程。如何確定TransactionScope已經調用了所有提交?

這是不符合的是明確規定的文件:

這種方法是同步的,會阻塞,直到事務被提交或中止。

更糟糕的是,似乎沒有辦法確定何時所有提交已處理。這是有問題的,因爲在控制檯應用程序中,主線程可以在處理後退出,並有效殺死所有後臺線程。分佈式事務中的遠程參與者永遠不會被通知這種情況,從而導致鎖保持打開,超時和其他醜陋的東西...

另一個問題是,當創建一個新的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 
+0

你是怎麼解決這個問題的?我有同樣的問題,也使用IBM MQ。 – cudima

+0

我添加了我實現的代碼作爲答案。這絕不是一個明確的解決方案,但它是我能找到的最好的解決方案。 –

回答

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

我知道這只是一個黑客,但直到有人給我一個更好的解決方案,這是最好的答案我可以來與...一起。

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

你是對的。這確實解決了示例中的問題,但您的解決方案對我的問題程序沒有幫助,其中'worker'是IBM Websphere MQ對象。所以我進一步研究了這一點,唯一的區別是MQ可以將自己註冊爲**持久**。 這似乎影響了TransactionScope的行爲,因爲'TransactionCompleted'事件現在似乎在不同於提交的線程**上被提升**,有時在實際調用提交之前收到。我在第一篇文章中改變了這個例子來證明這一點。 –

+0

您應該爲不同的事務使用不同的MQ對象。持久入伍意味着實施通知可能不會在幾分鐘或幾小時內到達。有關詳細信息,請參閱MSDN(http://msdn.microsoft.com/zh-cn/library/ms229975(v=vs.110).aspx)...在此期間,資源管理器對交易。它不知道交易是否承諾或中止。儘管資源管理器對交易持懷疑態度,但它通過保持交易鎖定來保持數據的修改,從而將這些更改與任何其他交易隔離開來。 – PashaPash

+0

好的,我可以同意這一點。在每次交易中重新創建一個MQ連接會帶來巨大的性能提升,但如果這提高了穩定性,我願意接受這一點。 –

相關問題