• 译文: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!

  • 相关阅读:
    如何快速定位到DBGrid的某一行!!!急...
    说说设计模式~单件模式(Singleton)
    EF架构~终于自己架构了一个相对完整的EF方案
    .Net——实现IConfigurationSectionHandler接口定义处理程序处理自定义节点
    在LINQ中实现多条件联合主键LEFT JOIN
    从LINQ开始之LINQ to Objects(上)
    WPF控件模板和数据模板
    WPF的ListView控件自定义布局用法实例
    初步探讨WPF的ListView控件(涉及模板、查找子控件)
    LINQ技巧:如何通过多次调用GroupBy实现分组嵌套
  • 原文地址:https://www.cnblogs.com/god--love-you/p/6076265.html
Copyright © 2020-2023  润新知