程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 談談分布式事務之三: System.Transactions事務詳解[下篇]

談談分布式事務之三: System.Transactions事務詳解[下篇]

編輯:關於.NET

在前面一篇給出的Transaction的定義中,信息的讀者應該看到了一個叫做DepedentClone的方法。該 方法對用於創建基於現有Transaction對象的“依賴事務(DependentTransaction)”。不像可提交事務 是一個獨立的事務對象,依賴事務依附於現有的某個事務(可能是可提交事務,也可能是依賴事務)。 依賴事務可以幫助我們很容易地編寫一些事務型操作,當環境事務不存的時候,可以確保操作在一個獨 立的事務中執行;當環境事務存在的時候,則自動加入其中。

一、依賴事務(Dependent Transaction)

依賴事務通過DependentTransaction類型表示,DependentTransaction定義如下。和 CommittableTransaction一樣,DependentTransaction也是Transaction的子類。既然 DependentTransaction依賴於現有的Transaction對象而存在,相當於被依賴事務的子事務,所以無法執 行對事務的提交,也自然不會定義Commit方法。但是,DependentTransaction具有一個唯一的方法成員 :Complete。調用這個方法意味著向被依賴事務發送通知,表明所有與依賴事務相關的操作已經完成。

1: [Serializable]
2: public sealed class DependentTransaction :  Transaction
3: {
4:     public void Complete();
5: }

1、通過DependentTransaction將異步操所納入現有事務

通過Transaction的靜態屬性Current表示的環境事務保存在TLS(Thread Local Storage)中,所以 環境事務是基於當前線程的。這就意味著,即使環境事務存在,通過異步調用的操作也不可能自動加入 到當前事務之中,因為在異步線程中感知不到環境事務的存在。在這種情況下,我們需要做的就是手工 將當前事務傳遞到另一個線程中,作為它的環境事務。通過依賴事務我們很容易實現這一點。

DependentTransaction通過Transaction的DependentClone方法創建,該方法具有一個 DependentCloneOption枚舉類型的參數,體現了被依賴的事務再上尚未接受到依賴事務的通知(調用 Complete或者Rollback方法)得情況下,提交或者完成所采取的事務控制行為。DependentCloneOption 提供了兩個選項,BlockCommitUntilComplete表示被依賴事務會一直等待接收到依賴事務的通知或者超 過事務設定的超時時限;而RollbackIfNotComplete則會直接將被依賴的事務回滾,並拋出 TransactionAbortedException異常。

1: [Serializable]
2: public class Transaction : IDisposable,  ISerializable
3: {
4:     //其他成員
5:     public  DependentTransaction DependentClone(DependentCloneOption cloneOption);
6: }
7:  public enum DependentCloneOption
8: {
9:     BlockCommitUntilComplete,
10:      RollbackIfNotComplete
11: }

下面的代碼演示了如果通過依賴事務,采用異步的方式進行銀行轉賬操作。借助於組件ThreadPool, 將主線程環境事務的依賴事務傳遞給異步操作代理,開始異步操作的時候將此依賴事務作為當前的環境 事務,那麼之後的操作將自動在當前事務下進行。

1: private static void Transfer(string accountFrom, string accountTo,  double amount)
2: {
3:     Transaction originalTransaction =  Transaction.Current;
4:     CommittableTransaction transaction = new  CommittableTransaction();
5:     try
6:     {
7:          Transaction.Current = transaction;
8:         ThreadPool.QueueUserWorkItem (state =>
9:         {
10:             Transaction.Current  = state as DependentTransaction;
11:             try
12:              {
13:                 Withdraw(accountFrom,  amount);
14:                 Deposite(accountTo, amount);
15:                  (state as DependentTransaction).Complete();
16:              }
17:             catch (Exception ex)
18:              {
19:                 Transaction.Current.Rollback (ex);
20:             }
21:             finally
22:              {
23:                 (state as  IDisposable).Dispose();
24:                 Transaction.Current =  null;
25:             }
26:         },  Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete));
27:          //其他操作
28:         transaction.Commit();
29:      }
30:     catch (TransactionAbortedException ex)
31:     {
32:          transaction.Rollback(ex);
33:         Console.WriteLine("轉帳失敗,錯 誤信息:{0}", ex.InnerException.Message);
34:     }
35:     catch  (Exception ex)
36:     {
37:         transaction.Rollback(ex);
38:          throw;
39:     }
40:     finally
41:     {
42:          Transaction.Current = originalTransaction;
43:          transaction.Dispose();
44:     }
45: }

由於在調用DependentClone方法創建依賴事務時指定的參數為 DependentCloneOption.BlockCommitUntilComplete,所以主線程在調用Commit方法提交事務的時候,由 於依賴事務尚未結束(調用Complete或者Rollback方法),在這裡會一直等待。如果依賴事務的 Complete或者Rollback一直沒有調用,那麼被依賴的事務會一直等到超出事務設置的超時時限。所以, 對於基於BlockCommitUntilComplete選項創建的依賴事務來說,應該及時地調用Complete或者Rollback 方法。

2、通過DependentTransaction實現事務型方法

這裡所說的事務型方法是指方法的執行總是在事務中執行。具體來講,有兩種不同的事務應用場景: 如果當前不存在環境事務,那麼方法的執行將在一個獨立的事務中執行;反之,如果存在環境事務,在 方法執行會自動加入到環境事務之中。

比如說,存儲(Deposit)和提取(Withdraw)就是典型的事務型操作。對於單純的存取款的場景, 應該創建一個新的事務來控制存儲和提取操作的執行,以確保單一帳戶款項的數據一致性。如果在轉賬 的場景中,應在在轉賬開始之前就創建一個新的事務,讓提取和存儲的操作自動加入到這個事務之中。

我們現在就結合可提交事務和依賴事務將Deposit和Withdraw兩個方法定義成事務型方法,為了相同 代碼的重復,在這裡把事務控制部分定義在如下一個InvokeInTransaction靜態方法中:

1: static void InvokeInTransaction(Action action)
2: {
3:      Transaction originalTransaction = Transaction.Current;
4:      CommittableTransaction committableTransaction = null;
5:      DependentTransaction dependentTransaction = null;
6:     if (null ==  Transaction.Current)
7:     {
8:         committableTransaction = new  CommittableTransaction();
9:         Transaction.Current =  committableTransaction;
10:     }
11:     else
12:     {
13:          dependentTransaction = Transaction.Current.DependentClone (DependentCloneOption.RollbackIfNotComplete);
14:         Transaction.Current  = dependentTransaction;
15:     }
16:
17:     try
18:      {
19:         action();
20:         if (null !=  committableTransaction)
21:         {
22:              committableTransaction.Commit();
23:         }
24:
25:          if (null != dependentTransaction)
26:         {
27:              dependentTransaction.Complete();
28:         }
29:     }
30:      catch (Exception ex)
31:     {
32:          Transaction.Current.Rollback(ex);
33:         throw;
34:     }
35:      finally
36:     {
37:         Transaction transaction =  Transaction.Current;
38:         Transaction.Current =  originalTransaction;
39:         transaction.Dispose();
40:     }
41: }

InvokeInTransaction方法的參數是一個Action類型的代理(Delegate),表示具體的業務操作。在 開始的時候記錄下當前的環境事務,當整個操作結束之後應該環境事務恢復成該值。如果存在環境事務 ,則創建環境事務的依賴事務,反之直接創建可提交事務。並將新創建的依賴事務或者可提交事務作為 當前的環境事務。將目標操作的執行(action)放在try/catch中,當目標操作順利執行後,調用依賴事 務的Complete 方法或者可提交事務的Commit方法。如果拋出異常,則調用環境事務的Rollback進行回滾 。在finally塊中將環境事務恢復到之前的狀態,並調用Dispose方法對創建的事務進行回收。

借助於InvokeInTransaction這個輔助方法,我們以事務型方法的形式定義了如下的兩個方法: Withdraw和Deposit,分別實現提取和存儲的操作。

1: static void Withdraw(string accountId, double amount)
2: {
3:      Dictionary<string, object> parameters = new Dictionary<string,  object>();
4:     parameters.Add("id", accountId);
5:      parameters.Add("amount", amount);
6:     InvokeInTransaction(() =>  DbAccessUtil.ExecuteNonQuery("P_WITHDRAW", parameters));
7: }
8:
9: static  void Deposit(string accountId, double amount)
10: {
11:      Dictionary<string, object> parameters = new Dictionary<string, object> ();
12:     parameters.Add("id", accountId);
13:     parameters.Add ("amount", amount);
14:     InvokeInTransaction(() =>  DbAccessUtil.ExecuteNonQuery("P_DEPOSIT", parameters));
15: }

二、TransactionScope

在上面一節,我結合可提交事務和依賴事務,以及環境事務的機制提供了對事務型操作的實現。實際 上,如果借助TransactionScope,相應的代碼將會變得非常簡單。下面的代碼中,通過 TransactionScope對InvokeInTransaction進行了改寫,從執行效果來看這和原來的代碼完全一致。

1: static void InvokeInTransaction(Action action)
2: {
3:      using (TransactionScope transactionScope = new TransactionScope())
4:      {
5:         action();
6:         transactionScope.Complete ();
7:     }
8: }

通過InvokeInTransaction方法前後代碼的對比,我們可以明顯看到TransactionScope確實能夠使我 們的事務控制變得非常的簡單。實際上,在利用System.Transactions事務進行編程的時候,我們一般不 會使用到可提交事務,對於依賴事務也只有在異步調用的時候會使用到,基於TransactionScope的事務 編程方式才是我們推薦的。

正如其名稱所表現的一樣,TransactionScope就是為一組事務型操作創建一個執行范圍,而這個范圍 始於TransactionScope創建之時,結束於TransactionScope被回收(調用Dispose方法)。在對 TransactionScope進行深入介紹之前,照例先來看看它的定義:

1: public sealed class TransactionScope : IDisposable
2: {
3:      public TransactionScope();
4:     public TransactionScope(Transaction  transactionToUse);
5:     public TransactionScope(TransactionScopeOption  scopeOption);
6:     public TransactionScope(Transaction transactionToUse,  TimeSpan scopeTimeout);
7:     public TransactionScope(TransactionScopeOption  scopeOption, TimeSpan scopeTimeout);
8:     public TransactionScope (TransactionScopeOption scopeOption, TransactionOptions transactionOptions);
9:      public TransactionScope(Transaction transactionToUse, TimeSpan scopeTimeout,  EnterpriseServicesInteropOption interopOption);
10:     public TransactionScope (TransactionScopeOption scopeOption, TransactionOptions transactionOptions,  EnterpriseServicesInteropOption interopOption);
11:
12:     public void  Complete();
13:     public void Dispose();
14: }

我們可以看到TransactionScope實現了IDisposable接口,除了Dispose方法之外,僅僅具有一個唯一 的方法:Complete。但是TransactionScope卻有一組豐富的構造函數。我們先來看看這些構造函數相應 的參數如何影響TransactionScope對事務控制的行為。

1、TransactionScopeOption

實際上前面一節中提供的InvokeInTransaction方法基本上體現了TransactionScope的內部實現。也 就是說,TransactionScope也是通過創建可提交事務或者依賴事務,並將其作為事務范圍內的環境事務 ,從而將范圍的所有操作納入到一個事務之中。

通過在構造函數中指定TransactionScopeOption類型的scopeOption參數,控制TransactionScope當 環境事務存在的時候應該采取怎樣的方式執行事務范圍內的操作。具有來講,具有三種不同的方式:

* 如果已經存在環境事務,則使用該環境事務。否則,在進入范圍之前創建新的事務;

* 總是為該范圍創建新事務;

* 環境事務上下文在創建范圍時被取消。范圍中的所有操作都在無環境事務上下文的情況下完成。

TransactionScopeOption是一個枚舉,三個枚舉值Required、RequiresNew和Suppress依次對應上面 的三種行為。

1: public enum TransactionScopeOption
2: {
3:     Required,
4:      RequiresNew,
5:     Suppress
6: }

對於Required選項,如果當前存在環境事務TransactionScope會創建環境事務的依賴事務,負責創建 可提交事務,然後將創建的環境事務或者可提交事務作為事務范圍的環境事務。如對於RequiresNew選項 ,TransactionScope總是會創建可提交事務並將其作為事務范圍的環境事務,意味著控制事務范圍內操 作的事務也當前的環境事務已經沒有任何關系。如果Suppress選項,TransactionScope會將事務范圍內 的環境事務設為空,意味著事務范圍內的操作並不受事務的控制。

Required是默認選項,意味著事務范圍內的事務將會作為當前環境事務的一部分。如果你不希望某個 操作被納入當前的環境事務,但是相應的操作也需要事務的控制以確保所操作數據的一致性。比如,當 業務邏輯失敗導致異常拋出,需要對相應的錯誤信息進行日志記錄。對於日記的操作就可以放入基於 RequiresNew選項創建TransactionScope中。對於一些不重要的操作(操作的錯誤可被忽略),並且不需 要通過事務來控制的操作,比如發送一些不太重要的通知,就可以采用Suppress選項。

2、TransactionOptions和EnterpriseServicesInteropOption

TransactionOptions在前面已經提及,用於控制事務的超時時限和隔離級別。對於超時時限,你也可 以選擇 TransactionScope相應能夠的構造函數以TimeSpan的形式指定。而對於事務的隔離級別,需要著 重強調一點:當選擇 TransactionScopeOption.Required選項時,TransactionScope指定的隔離級別必 須與環境事務(如果有)相匹配。

比如下面的例子中,我定義兩個嵌套的TransactionScope,外部的TransactionScope采用默認的隔離 級別,內部在采用ReadCommitted隔離級別,當執行這段代碼的時候,會拋出如圖1所示的 ArgumentException異常。

1: using (TransactionScope outerScope = new TransactionScope())
2:  {
3:     TransactionOptions transactionOptions = new TransactionOptions() {  IsolationLevel = IsolationLevel.ReadCommitted };
4:     using (TransactionScope  innerScope = new TransactionScope(TransactionScopeOption.Required,  transactionOptions))
5:     {
6:         //事務型操作
7:          innerScope.Complete();
8:     }
9:     //事務型操作
10:      outerScope.Complete();
11: }

圖1 隔離級別不一致導致的異常

實際上在System.Transactions事務機制被引入之前,像Enterprise Service主要依賴於基於COM+的 分布式事務。TransactionScope通過 EnterpriseServicesInteropOption控制System.Transactions事務 如何與COM+的分布式事務進行互操作。具有來講具有如下三種互操作選項,分別和 EnterpriseServicesInteropOption三個枚舉值相對應:

* None:Transaction 和 Current 之間不同步;

* Automatic:搜索現有的 COM+ 上下文並與之同步(如該上下文存在);

* Full:System.EnterpriseServices 上下文(可通過調用 ContextUtil 類的靜態方法 Transaction 來檢索)和 System.Transactions 環境事務(可通過調用 Transaction 類的靜態方法 Current 來檢索)始終保持同步。這將引入性能損失,因為可能需要創建新的 System.EnterpriseServices 上下文。

1: public enum EnterpriseServicesInteropOption
2: {
3:      None,
4:     Automatic,
5:     Full
6: }

3、事務提交和回滾

對於事務范圍中的事務,無論是事務的提交(對於可提交事務)、完成(依賴事務)和回滾都是在 Dispose方法中執行的。 TransactionScope中定一個個私有的布爾類型字段(complete)表示事務是否 正常結束。該成員的默認值為False,當調用 TransactionScope的Complete方法的時候會將此字段設置 成True。當Dispose執行的時候,如果該字段的值為False,會調用事務的Rollback方法對該事務實施回 滾;否則會調用Commit方法(對於可提交事務)對事務進行提交或者調用Complete方法(依賴事務)通 知被依賴的事務本地事務已經正常完成。

 

除了執行事務的提交、完成或者回滾之外,TransactionScope的Dispose方法還負責將環境事務回復 到事務范圍開始之前的狀態。在調用Complete和Dispose之前,環境事務處於不可用的狀態,如果此時試 圖獲取環境事務,會拋出異常。比如在下面的代碼中,在事務范圍內部調用 Complete方法後,通過 Transaction的Current靜態屬性獲取當前環境事務,會拋出圖2所示的InvalidOpertionException異常。

1: using (TransactionScope transactionScope = new TransactionScope())
2:  {
3:     //其他事務操作
4:     transactionScope.Complete();
5:      Transaction ambientTransaction = Transaction.Current;
6: }  

圖2 在TransactionScope完成之後獲取環境事務導致的異常

出處:http://artech.cnblogs.com

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved