3

완료하는 분산 트랜잭션 (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가 ** 내구성 **으로 등록된다는 점만 다를뿐입니다. 'TransactionCompleted'이벤트가 Commits와는 다른 스레드 **에서 발생하는 것으로 보이고 Commit이 실제로 호출되기 전에 때때로 수신되는 것처럼 TransactionScope의 동작에 영향을주는 것으로 보입니다. 나는 이것을 보여주기 위해 첫 번째 게시물에서 샘플을 변경했다. –

+0

다른 트랜잭션에 대해 다른 MQ 객체를 사용해야합니다. 영구 가입은 커밋 알림이 몇 시간 또는 몇 시간 동안 도착하지 않을 수 있음을 의미합니다. 자세한 내용은 MSDN (http://msdn.microsoft.com/en-us/library/ms229975(v=vs.110).aspx)을 참조하십시오. ...이 기간 동안 리소스 관리자는 다음과 같은 결과에 의문을 표시합니다. 거래. 트랜잭션이 커밋되었거나 중단되었는지는 알 수 없습니다. 리소스 관리자는 트랜잭션에 대해 의심이가는 동안 트랜잭션을 잠김 상태로 유지하여 데이터를 변경하지 않으므로 이러한 변경 사항을 다른 트랜잭션과 분리합니다. – PashaPash

+0

그래, 나는 그것에 동의 할 수있다. 각 트랜잭션에서 MQ 연결을 재 작성하는 것은 엄청난 성능 저하이지만 안정성이 향상되면이를 받아 들일 수 있습니다. –

관련 문제