2012-10-16 39 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方法中,我开始一个事务,然后运行测试代码,然后检查数据库内容。测试完成后,我将回滚事务以保持数据库处于原始状态。这种方法完美适用于单线程操作。但是当操作使用多于一个线程时,我需要在这些线程中共享事务。最好不需要更改生产代码。 –