• Async/Await模式中使用TransactionScope时,要设置参数为TransactionScopeAsyncFlowOption.Enabled枚举(转载)


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

    You might not know this, but the 4.5.0 version of the .NET Framework contains a serious bug regarding System.Transactions.TransactionScope and how it behaves with async/await. Because of this bug, a TransactionScope can't flow through into your asynchronous continuations. This potentially changes the threading context of the transaction, causing exceptions to be thrown when the transaction scope is disposed.

    This is a big problem, as it makes writing asynchronous code involving transactions extremely error-prone.

    The good news is that as part of the .NET Framework 4.5.1, Microsoft released the fix for that "asynchronous continuation" bug. The thing is that developers like us now need to explicitly opt-in to get this new behavior. Let's take a look at how to do just that.

    TL;DR

    • If you are using TransactionScope and async/await together, you should really upgrade to .NET 4.5.1 right away.
    • A TransactionScope wrapping asynchronous code needs to specify TransactionScopeAsyncFlowOption.Enabled in its constructor.
    Transaction, wrap me up!
     
    The System.Transactions.TransactionScope class allows you to wrap your database code, your infrastructure code and sometimes even third-party code (if supported by the third-party library) inside a transaction. The code then only performs the actions when you actually want to commit (or complete) the transaction.
    As long as all the code inside the TransactionScope is executed on the same thread, all the code on the call stack can participate with the TransactionScope defined in your code. You can nest scopes or create new independent scopes inside a parent transaction scope. You can even creates clones of a TransactionScope and pass the clone to another thread and join back onto the calling thread. By wrapping your code with a transaction scope, you are using an implicit transaction model, also called ambient transactions.
    An explicit transaction means we create a new instance of a CommittableTransaction in code and pass it from method to method as a parameter. Ambient or implicit means we wrap a code inside a TransactionScope. This sets the thread-static property Transaction.Current to a new instance of a CommittableTransaction.
    Traditional code using a TransactionScope looks like this:
    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);
    }
    As we can see outside the using block the Transaction.Current property is null. Inside the using block the Transaction.Current property is not null. Even a method in the call stack like SomeMethodInTheCallStack can access Transaction.Current as long as it is wrapped in the using block.
    The benefits of the TransactionScope is that the local transaction automatically escalates to a distributed transaction if necessary. The scope also simplifies programming with transactions if you favor implicit over explicit.
     
     
    TransactionFlowInterruptedException
     
    When async/await was introduced with C# 5.0 and .NET 4.5, one tiny little detail was completely forgotten. The underlying state machine introduced by the compiler didn't properly "float" the transaction around when an async method was called under a wrapping TransactionScope. Let's apply our knowledge of how TransactionScope works in synchronous code to asynchronous code. Now, if all we do is introduce an async method call inside the scope, you might expect that it would work just the same:
    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);
    }

    Unfortunately, it doesn't work that way. The code almost (but only almost) executes similarly to the synchronous version, but if the project this code is written in targets .NET Framework 4.5, when we reach the end of the using block and try to Dispose the TransactionScope the following exception is thrown:

    System.InvalidOperationException : A TransactionScope must be disposed on the same thread that it was created.

    To make TransactionScope and async work properly we need to upgrade our project to .NET 4.5.1.

    Let it flow

    With .NET 4.5.1 the TransactionScope has a new enumeration called TransactionScopeAsyncFlowOption which can be provided in the constructor. You have to explicitly opt-in the transaction flow across thread continuations by specifying TransactionScopeAsyncFlowOption.Enabled like this:

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

    If you were wondering, the default TransactionScopeAsyncFlowOption is Suppress; Microsoft wanted to avoid breaking codebases that assumed the old .NET 4.5.0 behavior.

    In the flow

    Even if you are not using NServiceBus but TransactionScope combined with async/await you should update all your code paths that are using a TransactionScope to include TransactionScopeAsyncFlowOption.Enabled. This enables the transaction to properly flow into asynchronous code, preventing business logic from misbehaving when used under a TransactionScope. This will save you many headaches.

    Be one with the flow!
     
    其实,在代码中就算没有使用Async/Await模式,也可以使用参数为TransactionScopeAsyncFlowOption.EnabledTransactionScope,因为就算是代码中没有使用Async/Await模式,参数为TransactionScopeAsyncFlowOption.EnabledTransactionScope也是不会报错的,所以无论何时,我们都应该使用构造函数的参数中有TransactionScopeAsyncFlowOption.Enabled枚举的TransactionScope,这样省去了很多麻烦,确保TransactionScope不会抛出异常。
     
     
     
  • 相关阅读:
    Java反射机制
    dd命令
    分区工具fdisk,gdisk,parted
    硬盘初识
    shell脚本之算术运算和逻辑运算
    linux防火墙简单的使用
    压缩解压打包工具基础
    find命令基础讲解
    个人数据备份方案
    数据库的表名字段名大小写问题
  • 原文地址:https://www.cnblogs.com/OpenCoder/p/12304882.html
Copyright © 2020-2023  润新知