2012-10-16 40 views
2

我有一個多線程事務和實體框架的問題。我有一個線程,它在事務中運行,我想在同一個事務中有更多的工作線程工作。下面的代碼說明了情況(在EF上下文中有一個虛擬實體,代碼基本上產生了5個線程,我想在每個線程中插入一些實體,並在主線程的末尾,我想繼續使用DB,但是以保持整個過程隔離在一個交易):多線程事務異常終止與實體框架

using(var scope = new TransactionScope()) 
{ 
    int cnt = 5; 
    ManualResetEvent[] evt = new ManualResetEvent[cnt]; 

    for(int i = 0; i < cnt; i++) 
    { 
     var sink = new ManualResetEvent(false); 
     evt[i] = sink; 

     var tr = Transaction.Current.DependentClone(
      DependentCloneOption.BlockCommitUntilComplete); 

     Action run =() => 
     { 
      using (var scope2 = new TransactionScope(tr)) 
      { 
       using (var mc = new ModelContainer()) 
       { 
        mc.EntitySet.Add(new Entity() 
        { 
         MyProp = "test" 
        }); 
        mc.SaveChanges(); 
       } 
      } 

      sink.Set(); 
     }; 

     ThreadPool.QueueUserWorkItem(r => run()); 
    } 

    ManualResetEvent.WaitAll(evt); 

    using (var mc = new ModelContainer()) 
    { 
     Console.WriteLine(mc.EntitySet.Count()); 
    } 
    Console.ReadKey(); 
} 

問題是,該異常引發mc.SaveChanges();.內部異常是TransactionException:「該操作對於事務狀態無效。」似乎在某個時候,交易被中止。我認爲這是第一次線程調用SaveChanges()後,但林不知道。任何想法爲什麼交易中止?

回答

1

我發現了什麼問題發生在這裏。根據this article,我發現在一個事務中同時處理兩個MSSQL服務器連接是不可能的。我還發現,我沒有正確處理以前的代碼中的依賴事務。我的工作圖示碼如下:

class Context 
    { 
     public ManualResetEvent sink; 
     public DependentTransaction transaction; 
    } 

    static object syncRoot = new object(); 

    static void Main(string[] args) 
    { 
     using (var scope = new TransactionScope()) 
     { 
      int cnt = 5; 
      ManualResetEvent[] evt = new ManualResetEvent[cnt]; 

      for (int i = 0; i < cnt; i++) 
      { 
       var sink = new ManualResetEvent(false); 
       evt[i] = sink; 

       var context = new Context() 
       { 
        // clone transaction 
        transaction = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete), 
        sink = sink 
       }; 

       ThreadPool.QueueUserWorkItem(new WaitCallback(Run), context); 
      } 

      // wait for all threads to finish 
      ManualResetEvent.WaitAll(evt); 

      using (var mc = new ModelContainer()) 
      { 
       // check database content 
       Console.WriteLine(mc.EntitySet.Count()); 
      } 

      // after test is done, the transaction is rolled back and the database state is untouched 
      Console.ReadKey(); 
     } 
    } 

    static void Run(object state) 
    { 
     var context = state as Context; 

     // set ambient transaction 
     Transaction oldTran = Transaction.Current; 
     Transaction.Current = context.transaction; 

     using (var mc = new ModelContainer()) 
     { 
      mc.EntitySet.Add(new Entity() 
      { 
       MyProp = "test" 
      }); 

      // synchronize database access 
      lock (syncRoot) 
      { 
       mc.SaveChanges(); 
      } 
     } 

     // release dependent transaction 
     context.transaction.Complete();    
     context.transaction.Dispose(); 

     Transaction.Current = oldTran; 

     context.sink.Set();    
    } 
} 

這可能不是編寫多線程經營業務層的非常好的方式,但這種共享事務處理方法是WERY在我的情況下,測試非常有用。唯一需要做這項工作的修改是覆蓋Db上下文,並在測試運行中同步保存方法。

0

一個SqlConnection不是線程安全的(和EF ObjectContext的/ DbContect不是線程安全的爲好),所以這個時候你同步訪問上下文和連接纔有效。你想出一個模型,並行處理CPU密集型的東西,並在所有線程完成後在一個線程中寫入所有更改。

+0

但我不明白的是,如果我沒有事務執行該代碼,它的工作原理。我正在爲每個線程創建新的數據庫上下文,並且我相信DependentTransaction應該用於跨線程共享事務 - [link](http://msdn.microsoft.com/zh-CN/LIbrary/system.transactions .dependenttransaction(v = vs.110)) –

+0

在仔細檢查你的代碼後,我看到你在做什麼。這確實可行,但我仍然認爲這是開發系統的一種可怕方式。你試圖解決什麼問題? – Steven

+0

這不是我的生產代碼的工作方式。我的目標是做一些單元集成測試。我正在測試針對MSSQL服務器的業務邏輯層。在測試init方法中,我開始一個事務,然後運行測試代碼,然後檢查數據庫內容。測試完成後,我將回滾事務以保持數據庫處於原始狀態。這種方法完美適用於單線程操作。但是當操作使用多於一個線程時,我需要在這些線程中共享事務。最好不需要更改生產代碼。 –