譯文: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 的副本,並將副本傳遞到另外一個線程並鏈接回調用線程。 經過使用事務做用域包裝代碼,使用隱式事務模型,也稱爲環境事務。async

以下代碼:分佈式

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 如何在同步代碼中工做的知識應用到異步代碼。spa

有以下代碼:.net

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!

相關文章
相關標籤/搜索