譯文:TransactionScope 與 Async/Await


你可能不知道這一點,在 .NET Framework 4.5.0  版本中包含有一個關於 System.Transactions.TransactionScope 在與 async/await 一起工作時會產生的一個嚴重的 bug 。由於這個錯誤,TransactionScope 不能在異步代碼中正常操作,它可能更改事務的線程上下文,導致在處理事務作用域時拋出異常。

這是一個很大的問題,因為它使得涉及事務的異步代碼極易出錯。

好消息是,在 .NET Framework 4.5.1 版本中,微軟發布了這個 "異步連接" 錯誤的修復程序。作為開發者的我們需要明確的做到以下兩點:

  • 如果說你在 TransactionScope 代碼中使用 async/await,你需要將框架升級到 .NET 4.5.1 或以上版本。
  • 在有包裝異步代碼的 TransactionScope 的構造函數中指定 TransactionScopeAsyncFlowOption.Enabled .

 

TransactionScope

System.Transactions.TransactionScope 類允許我們在事務中包裝數據庫代碼,基礎結構代碼,有時甚至包括第三方代碼(如果第三方庫支持)。 然后,代碼只在我們實際想提交(或完成)事務時才執行操作。

只要 TransactionScope 中的所有代碼都在同一線程上執行,調用堆棧上的所有代碼都可以與代碼中定義的 TransactionScope 一起參與。 我們可以在父事務作用域內嵌套作用域或創建新的獨立作用域。 我們甚至可以創建 TransactionScope 的副本,並將副本傳遞到另一個線程並連接回調用線程。 通過使用事務作用域包裝代碼,使用隱式事務模型,也稱為環境事務。

如下代碼:

public void TransactionScopeAffectsCurrentTransaction() {
    Debug.Assert(Transaction.Current == null);

    using (var tx = new TransactionScope()) {
        Debug.Assert(Transaction.Current != null);

        SomeMethodInTheCallStack();

        tx.Complete();
    }

    Debug.Assert(Transaction.Current == null);
}

private static void SomeMethodInTheCallStack()
{
    Debug.Assert(Transaction.Current != null);
}

正如我們可以看到使用塊外面的 Transaction.Current 屬性為 null。 在使用塊內 Transaction.Current 屬性不為 null。 即使在調用堆棧中的方法像 SomeMethodInTheCallStack 可以訪問 Transaction.Current,前提是它要被包裹在使用塊中。
TransactionScope 的好處是,如果需要,本地事務自動升級到分布式事務。 事務范圍還簡化了對事務的編程,如果你喜歡隱式的顯式。

 

TransactionFlowInterruptedException

當 async / await 引入了 C#5.0 和 .NET 4.5,一個小小的細節被完全忘記。 當在一個包裝 TransactionScope 下調用一個異步方法時,編譯器引入的底層狀態機沒有正確地“浮動”事務(原文 "float")。 讓我們將我們關於 TransactionScope 如何在同步代碼中工作的知識應用到異步代碼。

有如下代碼:

public async Task TransactionScopeWhichDoesntBehaveLikeYouThinkItShould() {
    using (var tx = new TransactionScope())
    {
        await SomeMethodInTheCallStackAsync()
            .ConfigureAwait(false);

        tx.Complete();
    }
}

private static async Task SomeMethodInTheCallStackAsync()
{
    await Task.Delay(500).ConfigureAwait(false);
}

不幸的是,它不工作的方式。 代碼幾乎(但只是幾乎)執行類似於同步版本,但如果項目這個代碼是寫在目標 .NET Framework 4.5,當我們到達使用塊的結束,並嘗試處置 TransactionScope 時拋出以下異常 :
  System.InvalidOperationException:一個 TransactionScope 必須處理在它被創建的同一個線程。

為了使TransactionScope和async正常工作,我們需要將我們的項目升級到.NET 4.5.1。

 

TransactionScopeAsyncFlowOption

在 .NET 4.5.1中,TransactionScope 有一個名為 TransactionScopeAsyncFlowOption 的新枚舉,可以在構造函數中提供。 您必須通過指定,明確地選擇跨線程連續的事務流,如下:

using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
    await SomeMethodInTheCallStackAsync()
        .ConfigureAwait(false);

    tx.Complete();
}

你可能很好奇,默認的 TransactionScopeAsyncFlowOption 是 Suppress(阻止的),因為微軟想避免破壞 .NET 4.5.0 版本中代碼庫中行為。

 

最后

使用 TransactionScope 結合 async / await 時,你應該更新所有使用 TransactionScope 的代碼路徑以啟用 TransactionScopeAsyncFlowOption.Enabled 。 這樣才能使事務能夠正確地流入異步代碼,防止在TransactionScope下使用時業務邏輯不正常。

 

原文:TransactionScope and Async/Await. Be one with the flow!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM