程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WCF:使用事務管理狀態和錯誤恢復

WCF:使用事務管理狀態和錯誤恢復

編輯:關於.NET

目錄

狀態管理和事務

單調用事務性服務

實例管理和事務

基於會話的服務和 VRM

事務性持久服務

事務性行為

向 IPC 綁定添加上下文

InProcFactory 和事務

編程中的一個根本問題就是錯誤恢復。發生錯誤後,應用程序必須自行恢復到產生錯誤之前的狀態。 請考慮這樣一個應用程序,它試圖執行一項由若干個更小操作組成的操作,這些小操作可能並行發生,而 且每個單獨操作的成功或失敗都與其他操作無關。任何一個更小操作出現錯誤,都意味著系統處於不一致 的狀態。

以銀行業務應用程序為例,該應用程序通過將資金劃入一個帳戶並從另一帳戶轉出資金,來在兩個帳 戶之間轉移資金。從一個帳戶轉出資金成功但將其劃入另一帳戶失敗就屬於不一致的狀態,因為資金無法 同時存在於兩個位置中;同樣,轉出資金失敗但劃入資金成功也會導致不一致狀態,此時資金失蹤。通常 總是由應用程序通過將系統恢復為原始狀態來從錯誤中恢復。

由於某些原因,做比說要難得多。首先,對於一項大的操作,如果大量排列部分成功、部分失敗,則 操作很快就會變得無法控制。這將導致代碼容易毀壞,使開發和維護費用變得非常昂貴,且代碼經常在實 際中不起作用,其原因是開發人員通常只處理易於恢復的情形,在此類情況下,他們能夠意識到問題所在 且知道如何處理。其次,您的復合操作可能是更大操作的一部分,即使您的代碼執行完美無缺,如果不屬 您控制范圍的事物出錯,您也必須撤消該操作。這意味著操作的管理和協作參與方之間需要緊密配合。最 後,還需將您的工作與其他需要和系統交互的人員隔離開來,因為如果您以後通過回滾某些操作來從錯誤 中恢復,會隱式將其他人員置於錯誤狀態中。

如您所見,手動編寫可靠的錯誤恢復代碼實際上是不可能的,這一點人們早已認識到了。自二十世紀 六十年代在業務環境中使用軟件起,人們就非常清楚必須要有一個更好的方法來管理恢復。這個方法就是 :事務。事務是一組操作,其任何一個操作的失敗都會導致整組失敗,就象一個原子操作一樣。使用事務 時,無需編寫恢復邏輯,因為沒有要恢復的內容。當所有操作均成功時,沒有要恢復的內容;當所有操作 均失敗時,不會對系統狀態產生任何影響,因此也沒有要恢復的內容。

使用事務時,關鍵是要使用事務性資源管理器。該資源管理器能夠在事務中止時回滾事務期間所發生 的全部更改,並在提交事務時保持更改。資源管理器還提供隔離功能;也就是說,當事務正在進行中時, 資源管理器會阻止所有方(包括事務在內)訪問事務和看到更改(這些更改仍能夠回滾)。這也意味著, 事務永遠不應訪問非資源管理器,因為在事務中止時對此類管理器所做的任何更改都不會回滾,這就需要 進行恢復。

傳統意義上,資源管理器為持久資源,如數據庫和消息隊列。然而,在 2005 年 5 月刊的《MSDN 雜 志》中,我在一篇名為“無法提交?.NET 中的不穩定資源管理器將事務引入公共類型”的文章中介紹了 實現常用不穩定資源管理器 (VRM)(名為 Transactional<T>)的技術:

public class Transactional<T> : ...
{
  public Transactional(T value);
  public Transactional();
  public T Value
  {get;set;}
  /* Conversion operators to and from T */
}

通過將任何可序列化類型參數(如 int 或 string)指定給 Transactional<T>,將此類型轉換 成完備的不穩定資源管理器,該管理器會自動參與到環境事務中、根據事務的結果提交或回滾更改,並將 當前更改與所有其他事務隔離開來。

圖 1 說明了 Transactional<T> 的用法。由於范圍不全、事務中止,number 和 city 的值會 還原成其事務之前的狀態。

圖 1 使用 Transactional<T>

Transactional<int> number = new Transactional<int>(3);
Transactional<string> city = new Transactional<string>("New York, ");
city.Value += "NY"; //Can use with or without transactions
using(TransactionScope scope = new TransactionScope())
{
  city.Value = "London, ";
  city.Value += "UK";
  number.Value = 4;
  number.Value++;
}
Debug.Assert(number == 3); //Conversion operators at work
Debug.Assert(city == "New York, NY");

除了 Transactional<T> 外,我還為 System.Collections.Generic 中的所有集合提供了一個 事務性數組和事務性版本,如 TransactionalDictionary<K,T>。這些集合與其非事務性同類是多 態關系,它們的用法完全相同。

狀態管理和事務

事務性編程的唯一目的是讓系統處於一致狀態。以 Windows Communication Foundation (WCF) 為例 ,系統狀態包括資源管理器以及服務實例的內存狀態。雖然資源管理器會在事務結果產生時自動管理其狀 態,但對於內存中對象或靜態變量而言,情況卻並非如此。

此狀態管理問題的解決方案是開發一項能夠感知狀態的服務,主動管理其狀態。在事務之間,服務應 將其狀態儲存在資源管理器中。在每個事務開始時,服務應從資源檢索其狀態,並通過此操作使資源參與 到事務中。在事務結束時,服務應將其狀態保存回資源管理器中。此技術可提供完善的狀態自動恢復功能 。對實例狀態所做的任何更改都會作為事務的一部分提交或回滾。

如果提交事務,則在服務下次獲取其狀態時,它將具有事務後狀態。如果中止事務,則下次它將具有 事務前狀態。無論采用何種方法,服務都將具有一致的狀態,可供新事務訪問。

編寫事務性服務時,還存在兩個其他問題。第一個問題是服務如何能夠知道事務開始和結束的時間, 以便它可以獲取和保存其狀態。服務可能屬於一個更大的事務,此類事務跨多個服務和機器。在兩次調用 之間的任一時刻,事務都有可能結束。誰將調用服務,讓服務知道要保存其狀態? 第二個問題必須利用 隔離來解決。不同的客戶端可能對不同的事務同時調用服務。服務如何將不同事務對其狀態的更改相互隔 離?如果另一個事務要訪問其狀態並根據它的值進行操作,則在原始事務中止且更改回滾時,此事務將處 於不一致的狀態。

這兩個問題的解決方案是使方法邊界與事務邊界相等。在每個方法調用開始時,服務應從資源管理器 讀取其狀態;在每個方法調用結束時,服務應將其狀態保存到資源管理器中。此操作可確保:如事務在兩 次方法調用之間結束,可保持服務狀態或將其回滾。此外,在每個方法調用中,在資源管理器內讀取和存 儲狀態可解決隔離難題,因為服務只讓資源管理器在並行事務之間隔離對狀態的訪問。

由於服務使方法邊界與事務邊界相等,因此服務實例還必須在每個方法調用結束時對事務的結果進行 表決。從服務角度而言,方法一旦返回,事務即會完成。在 WCF 中,這是通過 OperationBehavior 的 TransactionAutoComplete 屬性自動完成的。當此屬性設置為 true 時,如果操作中沒有未處理的異常, WCF 將自動表決提交事務。如果有未處理的異常,WCF 將表決中止事務。由於 TransactionAutoComplete 默認為 true,因此任何事務都將默認使用自動完成方法,如下所示:

//These two definitions are equivalent:
[OperationBehavior(TransactionScopeRequired = true,
          TransactionAutoComplete = true)]
public void MyMethod(...)
{...}
[OperationBehavior(TransactionScopeRequired = true)]
public void MyMethod(...)
{...}

有關 WCF 事務性編程的詳細信息,請參閱 2007 年 5 月刊的“基礎內容”專欄“WCF 事務傳播”。

單調用事務性服務

使用單調用服務時,一旦調用返回,實例即會銷毀。因此,用於儲存調用之間狀態的資源管理器必須 在實例的范圍之外。客戶端和服務還必須就哪些操作負責創建實例或從資源管理器中移除實例達成一致。

因為可能有相同服務類型的多個實例訪問同一個資源管理器,所以每個操作均必須包含某個特定參數 ,以允許服務實例在資源管理器中查找其狀態,並根據它進行綁定。我將此參數稱為實例 ID。客戶端還 必須調用專用操作,以從存儲中移除實例狀態。請注意,能夠感知狀態的事務性對象和單調用對象的行為 要求相同: 均在方法邊界檢索及保存其狀態。有了單調用服務,任何資源管理器均可用於儲存服務狀態 。您可以使用數據庫或者使用 VRM,如圖 2 所示。

圖 2 使用 VRM 的單調用服務

[ServiceContract]
interface IMyCounter
{
  [OperationContract]
  [TransactionFlow(TransactionFlowOption.Allowed)]
  void Increment(string instanceId);
  [OperationContract]
  [TransactionFlow(TransactionFlowOption.Allowed)]
  void RemoveCounter(string instanceId);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MyService : IMyCounter
{
  static TransactionalDictionary<string,int> m_StateStore =
                new TransactionalDictionary<string,int>();
  [OperationBehavior(TransactionScopeRequired = true)]
  public void Increment(string instanceId)
  {
   if(m_StateStore.ContainsKey(instanceId) == false)
    {
     m_StateStore[instanceId] = 0;
    }
    m_StateStore[instanceId]++;
    Trace.WriteLine(m_StateStore[instanceId]);
  }
  [OperationBehavior(TransactionScopeRequired = true)]
  public void RemoveCounter(string instanceId)
  {
   if(m_StateStore.ContainsKey(instanceId))
    {
     m_StateStore.Remove(instanceId);
    }
  }
}
//Client side:
MyCounterClient proxy = new MyCounterClient();
using(TransactionScope scope = new TransactionScope())
{
  proxy.Increment("MyInstance");
  scope.Complete();
}
//This transaction will abort since the scope is not completed
using(TransactionScope scope = new TransactionScope())
{
  proxy.Increment("MyInstance");
}
using(TransactionScope scope = new TransactionScope())
{
  proxy.Increment("MyInstance");
  proxy.RemoveCounter("MyInstance");
  scope.Complete();
}
proxy.Close();
//Traces:
1
2
2

實例管理和事務

WCF 強制服務實例使方法邊界與事務邊界相等並且能夠感知狀態,這意味著要在方法邊界清除它的所 有實例狀態。默認情況下,一旦事務完成,WCF 就會銷毀服務實例,確保內存中沒有能損害一致性的剩余 內容。

任何事務性服務的生命周期均由 ServiceBehavior 的 ReleaseServiceInstanceOnTransactionComplete 屬性控制。如果 ReleaseServiceInstanceOnTransactionComplete 設置為 true(默認值),它會在方法完成事務時處理 服務實例,這樣一旦涉及到實例編程模型,就會將任何 WCF 服務轉換成單調用服務。

這個笨拙的方法並非源自 WCF。從 MTS 開始,Microsoft 平台上分布的所有事務性編程模型均通過 COM+ 和 Enterprise Services 使事務性對象等同於單調用對象。這些技術的架構師只是不相信開發人員 能夠在面對事務(既復雜又不直觀的編程模型)時正確地管理對象的狀態。雖然大多數開發人員能夠更方 便地使用他們所熟悉的常規 Microsoft .NET Framework 對象的基於會話且有狀態的編程模型,但主要缺 點是所有想要從事務中受益的開發人員都必須采用有實際意義的單調用編程模型(請參見圖 2)。

我個人始終認為使事務等同於單調用實例化是不得不解決的棘手問題,然而從概念上講,它是被曲解 了。在需要可伸縮性時,人們應該只選擇單調用實例模式;理想情況下,事務應與對象實例管理和應用程 序對可伸縮性的考量相隔離。

如果需要擴展您的應用程序,則選擇單調用並使用事務的效果會很好。然而,如果您不需要可伸縮性 (大多數應用程序可能均如此),則不應允許您的服務以會話為基礎、有狀態且是事務性的。本專欄的其 余部分介紹了我的一項解決方案,它能啟用並保留基於會話的編程模型,同時配合使用事務與常用服務。

基於會話的服務和 VRM

WCF 通過將 ReleaseServiceInstanceOnTransactionComplete 設置為 false,允許使用事務性服務維 護會話語義。在此情況下,WCF 將置身事外,讓服務開發人員在面對事務時負責管理服務實例的狀態。單 會話服務仍必須使方法邊界與事務邊界相等,因為每個方法調用可能在不同的事務中,而且事務可能會在 同一會話中的兩次方法調用之間結束。雖然您能夠像使用單調用服務那樣手動管理此狀態(或使用不在本 專欄范圍內的某些其他高級 WCF 功能),但您也可以針對服務成員使用 VRM,如圖 3 所示。

圖 3 基於單會話事務性服務使用 VRM

[ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete = false)]
class MyService : IMyContract
{
  Transactional<string> m_Text = new Transactional<string>("Some initial value");
  TransactionalArray<int> m_Numbers = new TransactionalArray<int>(3);
  [OperationBehavior(TransactionScopeRequired = true)]
  public void MyMethod()
  {
    m_Text.Value = "This value will roll back if the transaction aborts";
    //These will roll back if the transaction aborts
    m_Numbers[0] = 11;
    m_Numbers[1] = 22;
    m_Numbers[2] = 33;
  }
}

VRM 的用途是產生有狀態的編程模型:服務實例只訪問其狀態,就如同未涉及任何事務一樣。對狀態 所做的任何更改都會利用事務來提交或回滾。然而,我發現圖 3 是一個專家編程模型,因此它有自己的 缺陷。它要求人們熟悉 VRM、成員的細致化定義以及規定,以將所有操作始終配置為需要事務,並禁止在 完成時釋放實例。

事務性持久服務

在 2008 年 10 月刊的這一專欄(“使用持久服務管理狀態”)中,我介紹了 WCF 為持久服務所提供 的支持。持久服務從已配置的存儲中檢索其狀態,然後在每次操作時將狀態保存回此存儲中。狀態存儲不 一定是事務性資源管理器。

如果是事務性服務,它當然應該只使用事務性存儲,並且應參與到每個操作的事務中。采用此方式時 ,如果事務中止,則狀態存儲會回滾到其事務之前的狀態。然而,WCF 不知道服務是否設計為將其事務傳 播到狀態存儲,而且在默認情況下,它不會讓存儲參與到事務中,即使存儲是事務性資源管理器,如 SQL Server 2005 或 SQL Server 2008。為指示 WCF 傳播事務並利用基礎存儲,請將 DurableService 的 SaveStateInOperationTransaction 屬性設置為 true:

[Serializable]
[DurableService(SaveStateInOperationTransaction = true)]
class MyService: IMyContract
{...}

SaveStateInOperationTransaction 默認為 false,因而狀態存儲不會參與事務。由於只有事務性服 務能夠從 SaveStateInOperationTransaction 設置為 true 中受益,因此如果它為 true,WCF 便會要求 服務上的所有操作將 TransactionScopeRequired 設置為 true 或具有強制事務流。如果配置操作時將 TransactionScopeRequired 設置為 true,則將使用操作的環境事務來利用存儲。

事務性行為

對於 DurableService 屬性而言,單詞“持久”不太恰當,因為它此時不一定指示持久行為。它所代 表的含義是 WCF 將自動從已配置的存儲中反序列化服務狀態,然後在每次操作時再次將其序列化。同樣 ,持久性提供程序行為不一定是指持久性,因為從預定抽象提供程序類衍生的任何提供程序都滿足持久性 。

實際上,持久服務基礎結構在現實中是序列化基礎結構,這使我可以在技術中利用這一結構,在出現 事務時管理服務狀態,同時在底層依賴不穩定資源管理器,而不必讓服務實例對其做任何事情。這樣進一 步改進了 WCF 的事務性編程模型,高級事務編程模型在純粹對象和常用服務方面的優勢也得以體現。

第一步是定義兩個事務性內存中提供程序工廠:TransactionalMemoryProviderFactory 和 TransactionalInstanceProviderFactory。TransactionalMemoryProviderFactory 使用靜態 TransactionalDictionary<ID,T> 儲存服務實例。目錄在所有客戶端和會話之間共享。只要主機在 運行,TransactionalMemoryProviderFactory 就允許客戶端連接服務或從服務斷開連接。使用 TransactionalMemoryProviderFactory 時,應指定一個完整的操作,以使用 DurableOperation 的 CompletesInstance 屬性從存儲中移除實例狀態。

另一方面,TransactionalInstanceProviderFactory 將每個會話與專用的 Transactional<T> 實例相匹配。不需要完成操作,因為在關閉會話後服務狀態將做為垃圾被收集起來。

接下來,我定義了 TransactionalBehavior 屬性,如圖 4 所示。TransactionalBehavior 是執行下 列配置的服務行為屬性。首先,它在 SaveStateInOperationTransaction 設置為 true 時向服務說明中 插入一個 DurableService 屬性。其次,它根據 AutoCompleteInstance 屬性的值,為持久性行為添加使 用 TransactionalMemoryProviderFactory 或 TransactionalInstanceProviderFactory 的功能。如果 AutoCompleteInstance 設置為 true(默認值),它將使用 TransactionalInstanceProviderFactory。 最後,如果 TransactionRequiredAllOperations 屬性設置為 true(默認值),TransactionalBehavior 會在所有服務操作行為上將 TransactionScopeRequired 設置為 true,進而為所有操作提供環境事務。 當它顯式設置為 false 時,服務開發人員可以選擇哪些操作將是事務性的。

圖 4 TransactionalBehavior 屬性

[AttributeUsage(AttributeTargets.Class)]
public class TransactionalBehaviorAttribute : Attribute,IServiceBehavior
{
  public bool TransactionRequiredAllOperations
  {get;set;}
  public bool AutoCompleteInstance
  {get;set;}
  public TransactionalBehaviorAttribute()
  {
    TransactionRequiredAllOperations = true;
    AutoCompleteInstance = true; 
  }
  void IServiceBehavior.Validate(ServiceDescription description,
                  ServiceHostBase host)
  {
    DurableServiceAttribute durable = new DurableServiceAttribute();
    durable.SaveStateInOperationTransaction = true;
    description.Behaviors.Add(durable);
    PersistenceProviderFactory factory;
    if(AutoCompleteInstance)
    {
     factory = new TransactionalInstanceProviderFactory();
    }
    else
    {
     factory = new TransactionalMemoryProviderFactory();
    }
    PersistenceProviderBehavior persistenceBehavior =
                 new PersistenceProviderBehavior(factory);
    description.Behaviors.Add(persistenceBehavior);
    if(TransactionRequiredAllOperations)
    {
     foreach(ServiceEndpoint endpoint in description.Endpoints)
     {
       foreach(OperationDescription operation in endpoint.Contract.Operations)
       {
        OperationBehaviorAttribute operationBehavior = 
          operation.Behaviors.Find<OperationBehaviorAttribute>();
        operationBehavior.TransactionScopeRequired = true;
       }
     }
    }
  }
  ...
}

使用 TransactionalBehavior 屬性及默認值時,不要求客戶端進行管理或以任何方式與實例 ID 交互 。對客戶端而言,必須要做的就是通過一種上下文綁定來使用代理,並讓綁定管理實例 ID,如圖 5 所示 。請注意,服務與作為其成員變量的正常整數進行交互。有趣的是,由於持久行為的緣故,取消實例激活 當然仍與方法邊界上的單調用服務類似,但編程模型是常用 .NET 對象的模型。

圖 5 使用 TransactionalBehavior 屬性

[ServiceContract]
interface IMyCounter
{
  [OperationContract]
  [TransactionFlow(TransactionFlowOption.Allowed)]
  void Increment();
}
[Serializable]
[TransactionalBehavior]
class MyService : IMyCounter
{
  int m_Counter = 0;
  public void Increment()
  {
    m_Counter++;
    Trace.WriteLine(m_Counter);
  }
}
//Client side:
MyCounterClient proxy = new MyCounterClient();
using(TransactionScope scope = new TransactionScope())
{
  proxy.Increment();
  scope.Complete();
}
//This transaction will abort since the scope is not completed
using(TransactionScope scope = new TransactionScope())
{
  proxy.Increment();
}
using(TransactionScope scope = new TransactionScope())
{
  proxy.Increment();
  scope.Complete();
}
proxy.Close();
//Traces:
1
2
2

向 IPC 綁定添加上下文

TransactionalBehavior 需要支持上下文協議的綁定。雖然 WCF 為基本綁定、Web 服務 (WS) 和 TCP 綁定提供上下文支持,但此列表中缺少的是進程間通信(IPC,也稱為管道)綁定。支持 IPC 綁定非常重 要,因為這樣可以通過 IPC 來使用 TransactionalBehavior,從而使熟知的調用從 IPC 中受益。為此, 我定義了 NetNamedPipeContextBinding 類:

public class NetNamedPipeContextBinding : NetNamedPipeBinding
{
  /* Same constructors as NetNamedPipeBinding */
  public ProtectionLevel ContextProtectionLevel
  {get;set;}
}

NetNamedPipeContextBinding 的使用方法與其基類完全相同。可以像任何其他內置綁定一樣,以編程 方式使用此綁定。然而,當在應用程序 .config 文件中使用自定義綁定時,需要通知 WCF 在何處定義自 定義綁定。雖然您可以按每個應用程序來執行此操作,但更簡單的做法是在 machine.config 中引用幫助 程序類 NetNamedPipeContextBindingCollectionElement,以影響機器上的每個應用程序,如下所示:

<!--In machine.config-->
<bindingExtensions>
  ...
  <add name = "netNamedPipeContextBinding"
     type = "ServiceModelEx.         NetNamedPipeContextBindingCollectionElement,
         ServiceModelEx"
  />
</bindingExtensions>

也可在工作流應用程序中使用 NetNamedPipeContextBinding。

圖 6 列出了 NetNamedPipeContextBinding 實現及其支持類中的一段摘錄(在本月的代碼下載中有完 整的實現)。NetNamedPipeContextBinding 的構造函數均將實際構造函數委托給 NetNamedPipeBinding 的基本構造函數,而且它們所做的唯一初始化工作就是設置上下文保護級別,使其默認為 ProtectionLevel.EncryptAndSign。

圖 6 實現 NetNamedPipeContextBinding

public class NetNamedPipeContextBinding : NetNamedPipeBinding
{
  internal const string SectionName = "netNamedPipeContextBinding";
  public ProtectionLevel ContextProtectionLevel
  {get;set;}
  public NetNamedPipeContextBinding()
  {
   ContextProtectionLevel = ProtectionLevel.EncryptAndSign;
  }
  public NetNamedPipeContextBinding (NetNamedPipeSecurityMode securityMode) :
                            base(securityMode)
  {
   ContextProtectionLevel = ProtectionLevel.EncryptAndSign;
  }
  public NetNamedPipeContextBinding(string configurationName)
  {
   ContextProtectionLevel = ProtectionLevel.EncryptAndSign;
   ApplyConfiguration(configurationName);
  }
  public override BindingElementCollection CreateBindingElements()
  {
    BindingElement element = new ContextBindingElement(ContextProtectionLevel,
               ContextExchangeMechanism.ContextSoapHeader);
    BindingElementCollection elements = base.CreateBindingElements();
    elements.Insert(0,element);
   return elements;
  }
  ... //code excerpted for space
}

任何綁定類的核心均為 CreateBindingElements 方法。 NetNamedPipeContextBinding 訪問其綁定元素的基本綁定集合,並向其添加 ContextBindingElement。 將此元素插入到集合中會添加對上下文協議的支持。

實現的其余內容僅僅是啟用管理配置的記錄。ApplyConfiguration 方法由構造函數調用,它使用綁定 內容的配置名稱。ApplyConfiguration 使用 ConfigurationManager 類從 .config 文件解析出 netNamedPipeContextBinding,並從中解析出 NetNamedPipeContextBindingElement 的實例。隨後調用 此綁定元素的 ApplyConfiguration 方法,用於配置綁定實例。

NetNamedPipeContextBindingElement 的構造函數會向其配置屬性的基類 Properties 集合中添加上 下文保護級別的單一屬性。在 OnApplyConfiguration(它作為 NetNamedPipeContextBinding.ApplyConfiguration 調用 ApplyConfiguration 的結果而被調用)中,方 法首先配置其基本元素,然後根據已配置的級別設置上下文保護級別。

NetNamedPipeContextBindingCollectionElement 類型用於綁定 NetNamedPipeContextBinding 與 NetNamedPipeContextBindingElement。采用此方式時,如果將 NetNamedPipeContextBindingCollectionElement 添加為綁定擴展,則配置管理器將知道要實例化哪個類 型並為其提供綁定參數。

InProcFactory 和事務

TransactionalBehavior 屬性允許您幾乎可以將應用程序中的每個類都視為事務性類,而不會損害熟 悉的 .NET 編程模型。弊端是 WCF 從未被設計為用於非常細化的層級——您將必須創建、打開並關閉多 個主機,而且您的應用程序 .config 文件將變得不能使用服務和客戶端分數進行管理。為解決這些問題 ,在我編寫的《Programming WCF, 2nd Edition》一書中,我定義了名為 InProcFactory 的類,它讓您 可以通過 WCF 來實例化服務類:

public static class InProcFactory
{
  public static I CreateInstance<S,I>() where I : class
                     where S : I;
  public static void CloseProxy<I>(I instance) where I : class;
  //More members
}

使用 InProcFactory 時,您在類級別利用 WCF,而無需憑借顯式管理主機或者具有客戶端或服務 .config 文件。為使每個類級別均能訪問 TransactionalBehavior 編程模型,InProcFactory 類使用 NetNamedPipeContextBinding,同時啟用事務流。使用圖 5 中的定義,InProcFactory 可啟用圖 7 中的 編程模型。

圖 7 結合使用 TransactionalBehavior 和 InProcFactory

IMyCounter proxy = InProcFactory.CreateInstance<MyService,IMyCounter>();
using(TransactionScope scope = new TransactionScope())
{
  proxy.Increment();
  scope.Complete();
}
//This transaction will abort since the scope is not completed
using(TransactionScope scope = new TransactionScope())
{
  proxy.Increment();
}
using(TransactionScope scope = new TransactionScope())
{
  proxy.Increment();
  scope.Complete();
}
InProcFactory.CloseProxy(proxy);
//Traces:
Counter = 1
Counter = 2
Counter = 2

圖 7 中的編程模型與純 C# 類的編程模型相同,沒有任何所有權開銷,另外代碼從事務中完全受益。 我認為這是為將來奠定基礎,未來的趨勢是內存本身就是事務性的,而且每個對象都可能是事務性的。

圖 8 顯示的是 InProcFactory 的實現,為了簡潔起見,已刪除了一些代碼。在每個應用程序域, InProcFactory 的靜態構造函數均調用一次,使用 GUID 在每個域中分配一個唯一的新基址。這使得 InProcFactory 可以在同一台機器上的多個應用程序域和進程中多次使用。

圖 8 InProcFactory 類

public static class InProcFactory
{
  struct HostRecord
  {
    public HostRecord(ServiceHost host,string address)
    {
     Host = host;
     Address = new EndpointAddress(address);
    }
    public readonly ServiceHost Host;
    public readonly EndpointAddress Address;
  }
  static readonly Uri BaseAddress = new Uri("net.pipe://localhost/" +
                       Guid.NewGuid().ToString());
  static readonly Binding Binding;
  static Dictionary<Type,HostRecord> m_Hosts = new Dictionary<Type,HostRecord> ();
  static InProcFactory()
  {
    NetNamedPipeBinding binding = new NetNamedPipeContextBinding();
    binding.TransactionFlow = true;
    Binding = binding;
    AppDomain.CurrentDomain.ProcessExit += delegate
                       {
             foreach(HostRecord hostRecord in m_Hosts.Values)
                         {
                         hostRecord.Host.Close();
                         }
                       };
  }
public static I CreateInstance<S,I>() where I : class
                     where S : I
  {
    HostRecord hostRecord = GetHostRecord<S,I>();
    return ChannelFactory<I>.CreateChannel(Binding,hostRecord.Address);
  }
  static HostRecord GetHostRecord<S,I>() where I : class
                      where S : I
  {
    HostRecord hostRecord;
    if(m_Hosts.ContainsKey(typeof(S)))
    {
     hostRecord = m_Hosts[typeof(S)];
    }
    else
    {
     ServiceHost host = new ServiceHost(typeof(S),BaseAddress);
     string address = BaseAddress.ToString() + Guid.NewGuid().ToString();
     hostRecord = new HostRecord(host,address);
     m_Hosts.Add(typeof(S),hostRecord);
     host.AddServiceEndpoint(typeof(I),Binding,address);
     host.Open();
    }
    return hostRecord;
  }
  public static void CloseProxy<I>(I instance) where I : class
  {
    ICommunicationObject proxy = instance as ICommunicationObject;
    Debug.Assert(proxy != null);
    proxy.Close();
  }
}

InProcFactory 在內部管理將服務類型映射為特定主機實例的字典。當調用 CreateInstance 創建特 定類型的實例時,它使用名為 GetHostRecord 的幫助程序方法在目錄中進行查找。如果字典尚未包含服 務類型,此幫助程序方法會為其創建一個主機實例,並使用新 GUID 作為唯一的管道名稱向此主機添加一 個端點。CreateInstance 然後從主機記錄獲取該端點的地址,並使用 ChannelFactory<T> 創建代 理。

在其靜態構造函數中(該函數在第一次使用類時調用),InProcFactory 訂閱進程退出事件以在進程 結束時關閉所有主機。最後,為了幫助客戶端關閉代理,InProcFactory 提供了 CloseProxy 方法,該方 法查詢 ICommunicationObject 的代理並將其關閉。要了解如何能夠利用事務性內存,請參閱“深入分析 ”側欄“什麼是事務性內存?”。

什麼是事務性內存?

您可能已經聽說過事務性內存,這是一種用於管理共享數據的新技術,許多人聲稱它可以解決在編寫 並發代碼時遇到的所有問題。或者您聽到的是,事務性內存言過其實,只不過是一個研究工具。真實情況 是它屬於兩種極端之間的事物。

事務性內存使您可以不必管理單個鎖。您可以用定義明確的序列塊(在數據庫中稱為工作單元或事務 )構建程序。然後,您可以讓基礎運行時系統、編譯器、硬件或它們的組合提供所需的隔離性和一致性保 證。

通常,基礎事務性內存系統能夠相當精細地提供最優的並發控制。事務性內存系統不必經常鎖定資源 ,它假定不存在爭用。它還檢測這些假定何時不成立,然後回滾在事務期間所做的任何試探性更改。事務 性內存系統然後可能會根據實現方式嘗試重新執行代碼塊,直到它能夠在無爭用的情況下完成。系統仍可 以檢測爭用並對其加以管理,不會要求您指定引起的回退或編寫代碼,您也不必重試機制。如果您有最優 的精細並發控制、爭用管理,且無需指定和管理特定的鎖,可以考慮使用那些利用並發的組件,以序列方 式解決您的問題。

事務性內存承諾提供復合功能,這是現有鎖定機制無法輕松實現的。要將多個操作或多個對象復合在 一起,一般需要增加鎖的粒度——通常是將這些操作或對象共同封裝在一個鎖下。事務性內存代表您的代 碼自動管理細粒度鎖定,同時又能避免死鎖,保證提供復合功能時不會損害可伸縮性,也不會引入死鎖。

目前,事務性內存尚未大規模實現商業化。使用庫、語言擴展、或編譯器指令的實驗軟件方法已在學 術界發表或在網站上發布。某些高端、高並發環境中,確實有可以提供有限事務性內存的硬件,但利用此 硬件的軟件沒有明確指出使用了這種硬件。事務性內存讓研究社區倍感興奮,您可期待此研究的一些成果 在未來十年內能夠使其成為更易於使用的產品。

附屬專欄描述了不穩定資源管理器的創建過程,您可以將該管理器與當前事務技術配合使用以提供原 子性(或全部執行,或都不執行)、後續可管理性、質量以及事務性編程所具有的其他優勢。事務性內存 提供相似的功能,但它對於任意數據類型使用相當輕量化的運行時軟件或硬件基元,並側重於提供可伸縮 性、隔離性、復合以及原子性,無需您創建自己的資源管理器。事務性內存得以廣泛使用,程序員將不僅 能從不穩定資源管理器(如更簡單的編程模型)中受益,還會獲得事務性內存管理器所帶來的可伸縮性收 益。

本文配套源碼

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