我遇到了多线程事务和实体框架的问题。我有一个在事务中运行的线程,我希望在同一个事务中有更多的工作线程。下面的代码说明了情况(在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()之后,但我不确定。知道交易中止的原因吗?
答案 0 :(得分: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();
}
}
编写多线程业务层可能不是很好的方法,但这种共享事务方法在我的案例中非常有用。进行此项工作的唯一修改是覆盖Db上下文并在测试运行中同步save方法。
答案 1 :(得分:0)
SqlConnection不是线程安全的(并且EF ObjectContext / DbContect也不是线程安全的),因此这仅在您同步对上下文和连接的访问时才有效。您想出了一个模型,您可以并行处理CPU密集型内容,并在所有线程完成后在一个线程中写入所有更改。