程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WCF中的Instance Management

WCF中的Instance Management

編輯:關於.NET

我們很容易理解在舊有編程模型中關於類實例的內容。設計模式中Singleton 也就是在描述著檔子事 。但基於WCF 並非適合於以上場景,Service 與Client 之間要保持良好的Instance 模型則需要依靠很多 其他機制。

Programming WCF Service Chapter4 對此進行了細致的描述。(更多細節請自行閱讀~)

WCF 支持三種類型的Instance 管理:

1. pre-call services :每個客戶端請求對應一個instance

2. Sessionful services :每個客戶端連接對應一個instance

3. Singleton services :所有客戶端共享一個instance

利用Behaviors 可以解決這方面的問題(還有一些其他基於“ 服務端” 的其他方面的問題可以通過 使用behaviors 來解決)。

注:客戶端是不知道服務端設置了什麼樣的behaviors 的。

VS2008MSDN :ms- help://MS.VSCC.v90/MS.MSDNQTR.v90.en/fxref_system.servicemodel/html/88efb135-d425-e5b1-57d6 -01a67158c1a5.htm

Apply the ServiceBehaviorAttribute attribute to a service implementation to specify service-wide execution behavior. (To specify execution behavior at the method level, use the OperationBehaviorAttributeattribute.) This attribute can be applied only to service implementations.

ServiceBehaviorAttribute :僅應用於服務實現。

OperationBehaviorAttribute :用於方法級別。

//瞧這裡什麼屬性都沒有
public interface IMyContract
{}
//而是設置在了具體服務實現上
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyContract : IMyContract,IDisposable
{}

設置instance 模式類型由ServiceBehaviorAttribute 的屬性InstanceContextMode 進行設置,默認 值為 PerSession.

Per-Call Services

只有當客戶端調用的時候才有instance 。

為了說明問題,書中用了很形象的例子。

Code:

public interface IMyContract
{
   [OperationContract]
   string GetData(int value);
   [OperationContract]
   CompositeType GetDataUsingDataContract(CompositeType composite);
   // TODO: Add your service operations here
   [OperationContract]
   void Count();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class MyContract : IMyContract,IDisposable
{
   //Other Members
   public MyContract()
   {
     Trace.WriteLine("WcfServiceLibrary1.MyContract()");
   }
   #region IMyContract Members
   int count = 0;
   public void Count()
   {
     count++;
     Trace.WriteLine("Counter = " + count);
   }
   #endregion
   #region IDisposable Members
   public void Dispose()
   {
     Trace.WriteLine("WcfServiceLibrary1.Dispose()");
   }
   #endregion
}
//Tester

ServiceReference1.MyContractClient proxy = new  ConsoleApplication1.ServiceReference1.MyContractClient();

proxy.Count();
proxy.Count();
proxy.Count();

proxy.Close();
Console.ReadKey();

結果為:

WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()

很明顯,每次的值都是0+1 的結果,這正說明了percall 的方式是每個請求一個Instance 的。

Per-Session Services

修改上面的例子:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]

為:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]

結果為:

WcfServiceLibrary1.MyContract()
Counter = 1
Counter = 2
Counter = 3
WcfServiceLibrary1.Dispose()

很明顯Instance 只有一個了。

我們WcfServiceLibrary 默認的Bind 是wsHttpBinding ,但若是basicHttpBinding ,由於每個http 到達服務端都是一個新的連接,因此服務端無法判斷是哪個連接。

增加服務端app.config 中Endpoint 。

<endpoint address="basic" binding="basicHttpBinding" name="basic"  contract="WcfServiceLibrary1.IMyContract" />

重新導入後修改Program 裡的程序:

ServiceReference1.MyContractClient proxy = new  ConsoleApplication1.ServiceReference1.MyContractClient("basic");

ServiceReference1.MyContractClient proxy = new  ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");

其中basic 和WSHttpBinding_IMyContract 為兩種不同形式的服務在客戶端的Endpoint.Name 。

之前默認WSHttpBinding_IMyContract ,現在由於存在多個Endpoint ,則需要顯示指定。

現指定為basic 。再次運行,結果:

WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()

其結果與PerCall 是相同的。

通過SessionId 可以獲得Instance 的SessionId

使用Per-Session 方式可以通過設置SessionMode 屬性(允許、必須、不允許三種枚舉)。

SessionMode :Gets or sets a value that indicates whether a session is required by the contract.

SessionMode 枚舉http://msdn2.microsoft.com/zh- cn/library/system.servicemodel.sessionmode.aspx

Allowed( 允許)
Specifies that the contract supports sessions if the incoming binding supports them.
如果綁定支持Session 的話,則讓其支持,否則按照可以支持的方式,比如PerCall 的方式進 行支持。 Required( 必須)
Specifies that the contract requires a sessionful binding. An exception is thrown if the binding is not configured to support session.
指定契約必須使用Sessionful 的方式。如果 不支持,則拋出異常。 NotAllowed( 不允許)
Specifies that the contract never supports bindings that initiate sessions.

指定不能使用Sessionful 的方式。作者推薦是用NotAllowed 的時候僅用PerCall 方式。

剛才由於我添加了basic 的方式,因為默認選中Allowed ,因此剛才的之所以結果與PerCall 相同, 是因為它,下面我將其修改為Required 。

[ServiceContract]

修改為

[ServiceContract(SessionMode=SessionMode.Required)]

結果為一個運行時錯誤:

System.InvalidOperationException: Contract requires Session, but Binding  'BasicHttpBinding' doesn't support it or isn't configured properly to support  it.
   at System.ServiceModel.Description.DispatcherBuilder.BuildChannelListener (StuffPerListenUriInfo stuff, ServiceHostBase serviceHost, Uri listenUri,  ListenUriMode listenUriMode, Boolean supportContextSession, IChannelListener&  result)
   at System.ServiceModel.Description.DispatcherBuilder.InitializeServiceHost (ServiceDescription description, ServiceHostBase serviceHost)
   at System.ServiceModel.ServiceHostBase.InitializeRuntime()
   at System.ServiceModel.ServiceHostBase.OnOpen(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Open()
   at Microsoft.Tools.SvcHost.ServiceHostHelper.OpenService(ServiceInfo info)

但是若使用wsHttpBinding ,但卻without security and without reliable messaging 也將無法維 持transport-level session 。

添加如下代碼到app.config(Service) (指定其為無安全並且不可靠消息)

<system.serviceModel><!-- Other service Model -->

  <bindings>
    <wsHttpBinding>
     <binding name="wsBinding">
      <reliableSession enabled="false" />
      <security mode="None" />
     </binding>
    </wsHttpBinding>
   </bindings>
  </system.serviceModel>

<endpoint address="" binding="wsHttpBinding"  contract="WcfServiceLibrary1.IMyContract" bindingConfiguration="wsBinding" >

增加綁定配置為wsBinding

確保服務端程序為:

[ServiceContract(SessionMode = SessionMode.Allowed)]

( 或者沒設)

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]

運行結果與PerCall 的結果相同。其原因也就是因為wsHttpBinding 未設置安全可靠的Session 。

超時

inactivityTimeout :超時時間

在連接空閒的情況下,以客戶端和服務端的超時時間中最短的那個來決定是否移除Instance ,若之後 再調用則會拋出異常。

作者額外注明可以采用:AutomaticSessionShutdown 屬性。其設置為true 則當proxy.Close() 的時 候自動關閉Session ,設為false 的時候則只有在服務端將服務關閉才會關閉Session 。

但是,若將其修改為NotAllowed

則結果與PerCall 相同( 手動寫為PerSession) 。( 不管服務配置如何,它總會是PerCall 。因為TCP 和IPC 協議總是維持transport level ,你不能將它們配置SessionMode.NotAllowed ,它們會在服務載 入時進行驗證。作者建議是“ 在選擇使用SessionMode.NotAllowed 的同時,將服務配置為PerCall” 。 )

Singleton Service

Singleton ,顧名思義就是僅有一個Instance ,供所有客戶端調用。

在說明問題之前先修改上面的例子:

//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
為:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]

Tester 中:

static void Main(string[] args)
{
   ServiceReference1.MyContractClient proxy = new  ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");
   proxy.Count();
   proxy.Count();
   proxy.Count();
   Console.WriteLine(proxy.Endpoint.Name);
   Console.WriteLine(proxy.InnerChannel.SessionId);
   proxy.Close();
   Console.WriteLine(proxy.Endpoint.Name);
   Console.WriteLine(proxy.InnerChannel.SessionId);
   Console.WriteLine("_________________________________________________________");
   Console.ReadKey();
   ServiceReference1.MyContractClient proxy1 = new  ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");
   proxy1.Count();
   proxy1.Count();
   proxy1.Count();
   Console.WriteLine(proxy1.Endpoint.Name);
   Console.WriteLine(proxy1.InnerChannel.SessionId);
   proxy1.Close();
   Console.WriteLine(proxy1.Endpoint.Name);
   Console.WriteLine(proxy1.InnerChannel.SessionId);
   Console.WriteLine("_________________________________________________________");
   Console.ReadKey();
   ServiceReference1.MyContractClient proxy2 = new  ConsoleApplication1.ServiceReference1.MyContractClient("basic");
   proxy2.Count();
   proxy2.Count();
   proxy2.Count();
   Console.WriteLine(proxy2.Endpoint.Name);
   Console.WriteLine(proxy2.InnerChannel.SessionId);
   proxy2.Close();
   Console.WriteLine(proxy2.Endpoint.Name);
   Console.WriteLine(proxy2.InnerChannel.SessionId);
   Console.WriteLine("_________________________________________________________");
   Console.ReadKey();
}

運行的結果:(Output<Debug>)

// 客戶端調用前 
//...
// 其他代碼
//...
WcfServiceLibrary1.MyContract()
//...
// 其他代碼
//...
// 客戶端調用後
//...
// 其他代碼
//...
Counter = 1
Counter = 2
Counter = 3
Counter = 4
Counter = 5
Counter = 6
Counter = 7
Counter = 8
Counter = 9
//...
// 其他代碼
//...

從Counter 的值看來,多個proxy 調用的是同一個Instance 。

值得一提的是WcfServiceLibrary1.MyContract() ,也就是構造函數的調用時間是在Service 啟動的 時候,而PerCall 與Sessionful 構造函數調用時間都是在proxy 調用之時。而且只有當Host 關閉的時候 才會Dispose() 。

MSDN :

If the InstanceContextModevalue is set to Single the result is that your service can only process one message at a time unless you also set the ConcurrencyModevalue to Multiple.

也就是說除非將服務設置成多線程的,否則在一個時間只能處理一個消息請求。

從HOST 端控制SingletonInstance

方式一:

修改代碼:

static void Main(string[] args)
  {
    Uri baseAddress = new Uri (http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary1/MyContract/);
    WcfServiceLibrary1.MyContract singleInstance = new  WcfServiceLibrary1.MyContract();
    singleInstance.CountProperty = 200;//設置初始值200
    System.ServiceModel.ServiceHost host = new System.ServiceModel.ServiceHost (singleInstance);
    host.AddServiceEndpoint(typeof(WcfServiceLibrary1.IMyContract), new  WSHttpBinding(), baseAddress);
    host.Open();
    Console.WriteLine("host state = {0}", host.State.ToString());
    ServiceReference1.MyContractClient proxy = new  ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");
    proxy.Count();
    proxy.Count();
    singleInstance.CountProperty = 100;//修改singletonInstance.CountProperty為100 
    proxy.Count();
    Console.WriteLine(proxy.Endpoint.Name);
    Console.WriteLine(proxy.InnerChannel.SessionId);
    proxy.Close();
    Console.WriteLine(proxy.Endpoint.Name);
    Console.WriteLine(proxy.InnerChannel.SessionId);
    Console.WriteLine("_________________________________________________________");
    Console.ReadKey();
    host.Close();
  }

public class MyContract

添加屬性:

public int CountProperty 
{
   set
   {
     count = value;
   }
}

無須啟動WcfServiceLibrary1 (將用外部Host 進行啟動)直接運行客戶端程序:

結果:

// 客戶端調用前
//...
// 其他代碼
//...
WcfServiceLibrary1.MyContract()
//...
// 其他代碼
//...
// 客戶端調用後 
//...
// 其他代碼
//...
Counter = 201
Counter = 202
Counter = 101
//...
// 其他代碼
//...

方式二:

通過OperationContext.Current.Host 來獲取當前進程的host

此方法我暫時未調出來,大家有想到或做到的麻煩告訴我!

ServiceHost host = OperationContext.Current.Host as ServiceHost;
MyContract singletonInstance = host.SingletonInstance as MyContract;
if (singletonInstance != null)
   singletonInstance.CountProperty = DateTime.Now.Second;

先假設以上方法可行吧。

現在我遇到的問題:

1. OperationContext.Current 這裡的(MSDN:Gets or sets the execution context for the current thread.)current thread 是指我ConsoleApplication 也就是Client 的線程呢,還是指 Service 端的線程?(據我常理分析應該是服務端的線程)。理論上我應該在Count() 方法內寫這段代碼 ,但是問題又涉及到通過Client 端進行調用時是使用proxy ,這樣真正的情況會是怎樣呢?

2. 因此我又寫了一個在一個ConsoleApplication 裡完成服務的代碼,此時在 host.SingletonInstance 的確是被賦值了( 不再是null 了) ,但是緊接著我調用 OperationContext.Current 卻發現其為null ,也就是說這裡的OperationContext 並沒有被賦值。繼而 將其轉移到同在一個程序內的MyContract.Count() 方法中,但是其值仍然為空,因此此法再度失效。

希望作為高手的您能夠提供一個使用OperationContext.Current 的場景。什麼樣算是 OperationContext 的當前線程?

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