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

相关文章
相关标签/搜索