你可能不知道這一點,在 .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下使用時業務邏輯不正常。