EF 多线程TransactionScope事务异常"事务(进程 ID 58)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。"

时间:2024-04-02 14:52:13

在使用EF的TransactionScope事务时,如果多线程程序,经常会抛出如下异常

{“事务(进程 ID 58)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。”}

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);
}

https://blog.csdn.net/u011127019/article/details/54576873