在使用EF的TransactionScope事务时,如果多线程程序,经常会抛出如下异常
{“事务(进程 ID 58)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。”}
同一个TransactionScope逻辑操作事务在多线程中启动时会抛出异常。
解决方案:
使用线程锁,对同一个事务操作,仅允许一个线程执行
示例说明
1.出现异常的代码
事务操作定义
using (var tran = new TransactionScope())
{
ModuleOperate _module = new ModuleOperate();
//1.修改模块名称
_module.UpdateFirstName("模块:" + name);
//2.修改菜单
this.UpdateFirstName("菜单:" + name);
//提交事务
tran.Complete();
}
多线程调用定义
Action<object> update1 = (number) =>
{
while (true)
{
//将上线文实例放在本线程中创建
MenuOperate _menu = new MenuOperate();
_menu.UpdateName(Count.ToString());
Console.WriteLine("-------");
Console.WriteLine(_menu.GetName2());
Count++;
Thread.Sleep(1000 * Convert.ToInt32(number));
}
};
for (int i = 0; i < 3; i++)
{
Task.Factory.StartNew(update1, i + 1);
}
2.解决方案代码一:使用lock锁定
//对于锁推荐使用静态私有静态变量
private readonly static object _MyLock = new object();
/// <summary>
/// 事务, 多表修改
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public bool UpdateName(string name)
{
lock (_MyLock)
{
using (var tran = new TransactionScope())
{
ModuleOperate _module = new ModuleOperate();
1.修改模块名称
_module.UpdateFirstName("模块:" + name);
2.修改菜单
this.UpdateFirstName("菜单:" + name);
提交事务
tran.Complete();
}
}
return true;
}
3.解决方案代码二:使用Monitor封装TransactionScope
使用代码:
using (var tran = new EFTransaction())
{
//修改名称
name = ">>ModuleOperate:" + name;
UpdateFirstName(name);
//2.修改菜单
MenuOperate _menu = new MenuOperate();
_menu.UpdateFirstName(name);
//提交事务
tran.Commit();
}
EFTransaction类定义:
/// <summary>
/// 自定义事务处理,
/// 此版本,数据库上下文会出现多个,所以事务使用 TransactionScope
/// 使用排它锁,确保事务的单线程执行
/// </summary>
public class EFTransaction : IDisposable
{
private readonly static object _MyLock = new object();
/// <summary>
/// 当前事务对象
/// </summary>
private TransactionScope tran = null;
public EFTransaction()
{
Monitor.Enter(_MyLock);//获取排它锁
this.tran = new TransactionScope();
}
/// <summary>
/// 提交
/// </summary>
public void Commit()
{
tran.Complete();
}
/// <summary>
/// 混滚操作,在Dispose(),中自动调用回滚
/// </summary>
public void Rollback()
{
//提前执行释放,回滚
if (tran != null)
tran.Dispose();
}
public void Dispose()
{
if (tran != null)
tran.Dispose();
Monitor.Exit(_MyLock);//释放排它锁
}
}
使用验证代码:不同线程对于同一个事务操作多个事务实例,在当前程序中事务操作代码顺序同步执行,不会出现异常和数据异常。
Action<object> update1 = (number) =>
{
while (true)
{
//同一个线程使用多个事务
MenuOperate _menu = new MenuOperate();
ModuleOperate _module = new ModuleOperate();
////事务操作一
_menu.UpdateName(Count.ToString());
Thread.Sleep(Count); //错开等待时间,测试多线程异步问题
//事务操作二
_module.UpdateName(Count.ToString());
Console.WriteLine("-------");
Console.WriteLine(_menu.GetName2());
Count++;
Thread.Sleep(1000 * Convert.ToInt32(number));
}
};
for (int i = 0; i < 3; i++)
{
Task.Factory.StartNew(update1, i + 1);
}