你可能不知道这一点,在 .NET Framework 4.5.0 版本中包含有一个关于 System.Transactions.TransactionScope 在与 async/await 一块儿工做时会产生的一个严重的 bug 。因为这个错误,TransactionScope 不能在异步代码中正常操做,它可能更改事务的线程上下文,致使在处理事务做用域时抛出异常。数据库
这是一个很大的问题,由于它使得涉及事务的异步代码极易出错。编程
好消息是,在 .NET Framework 4.5.1 版本中,微软发布了这个 "异步链接" 错误的修复程序。做为开发者的咱们须要明确的作到如下两点:框架
TransactionScopeAsyncFlowOption.Enabled .
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 的好处是,若是须要,本地事务自动升级到分布式事务。 事务范围还简化了对事务的编程,若是你喜欢隐式的显式。函数
当 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。
在 .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下使用时业务逻辑不正常。