天天看點

TransactionScope線程安全問題整理

一、關于TransactionScope

1.在使用事務操作是,當程式中存在多個EF上下文,很多時候都是使用TransactionScope

2.TransactionScope使用簡單,同一個事務中多個資料庫上下文不會出現程式死鎖

實驗證明:

1.多線程中不同的事務鎖定了相同的表會抛出異常

2.抛出異常的線程将自動結束

3.如果出現資料庫表死鎖,本程式的其他線程中如果也有關于鎖定表的操作同樣的會暫停等待一段時間

4.使用TransactionScope,鎖定表的表同樣會影響其他程序或者線程的查詢操作,其他程序的查詢樣會挂起等待

二、關于使用TransactionScope多線程問題解決方案

解決方案1(推薦)

  1.所有的事務使用同一個線程鎖,也就是說多個線程中事務操作開啟同一個時間隻有一個

  2.這種處理方式簡單,不會抛出異常,但會稍微有點影響性能,因為一個事務開啟的話,其他的事務操作會挂起等待

  3.特别說明,如果已知某些事務操作會執行比較長的時間,這種方式就不太合适了

代碼執行個體:

使用封裝過的TransactionScope,此處使用公共的排它鎖,控制事務執行個體的建立和釋放之間的代碼,同一時間隻有一個在運作。

也就是說隻有一個事務釋放之後,才會建立另一個事務。

/// <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);//釋放排它鎖
    }
}
           

解決方案2

  1.寫代碼的時候特别注意,不同僚務之間不能操作相同的表,也就是多個事務中鎖定的表不能有重合,否則就會抛出死鎖異常

  2.這種方式對編碼有要求,并且控制上相對比較難

  3.這種方式性能比較高,因為在邏輯上排除了資料庫表死鎖

解決方案3(推薦)

  1.在所有的事務中都退死鎖異常進行處理,也就是使用try/catch 處理復原,本次操作失敗,下次在重新執行此業務邏輯

  2.這種方式比較折中,而且還可以在此一并處理其他的異常

  3.如果使用這種方式的話,開啟事務參數,最好指定等待時間

代碼執行個體:

此方案,一個事務操作一個鎖,如果抛出死鎖異常,本次操作失敗,依賴下一次操作。

//對于鎖推薦使用靜态私有靜态變量
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())
        {
            try
            {
                ModuleOperate _module = new ModuleOperate();
                //1.修改子產品名稱
                _module.UpdateFirstName("子產品:" + name);
                System.Threading.Thread.Sleep(100);
                //2.修改菜單
                this.UpdateFirstName("菜單:" + name);
                //送出事務
                tran.Complete();
            }
            catch (Exception ex)
            {
                //使用TransactionScope,如果沒有送出自動處理
                //執行復原操作處理
                Console.WriteLine("目前操作Menu");
                Console.WriteLine(ex.InnerException == null ? ex.Message : ex.InnerException.Message);
            }
        }
    }
    return true;
}
           

更多參考:

EntiryFramework中事務操作(二)TransactionScope

EF 多線程TransactionScope事務異常

C# lock關鍵詞/lock語句塊、線程鎖

EntiryFramework中事務操作(三)事務復原資料模型和資料庫不對應問題