WCF服務注意問題之-回調
在使用服務時雖然一般是客戶端向服務端請求服務,但有些時候也需要服務端向客戶端進行通知(Notify),在CS的程序中尤爲常見,在出現WCF之前,Remoting中使用回調是大費周章的一件事情,需要建立單獨的偵聽類,並且要處於獨立的程序集中才行,在WCF中大大簡化了回調過程,但是也有一些需要特別注意的地方,如果不注意回調也不是那麼容易的,先看一個簡單的回調代碼:
一:配置屬性聲明:
服務接口:
[ServiceContract(CallbackContract = typeof(IChartCallBack))] public interface IChart
{
[OperationContract]
void Join(string name);
[OperationContract]
void Leave(string name);
[OperationContract]
void Speek(string name, string desname, string message );
}
服務端實現:
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant,
InstanceContextMode = InstanceContextMode.PerCall)]
public abstract class Chart: IChart
{
public void Join(string name)
{
//……code
IChartCallBack callback = OperationContext.Current.GetCallbackChannel<IChartCallBack>();
if (callback != null)
{
callback.NotifyJoin(name); //通知客戶端
}
}
//剩餘代碼
}
回調接口:
[ServiceContract]
public interface IChartCallBack
{
[OperationContract(IsOneWay = true)]
void NotifyJoin(string name);
[OperationContract(IsOneWay = true)]
void NotifyLeave(string name);
[OperationContract(IsOneWay = true)]
void SpeekTo(string name, string message);
}
客戶端實現:
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant,UseSynchronizationContext=false)]
public class Client: IChartCallBack //實現了聊天回調窗口
{
public void NotifyJoin(string name)
{
Console.WriteLine(name + ” 已加入”);
}
}
上面的代碼都很簡單,用起來也極爲簡便,但是要注意的地方還是很多,如果不注意這些問題就會出差錯,如上面的代碼所示回調基本上是在聲明式代碼中完成的,屬性又有不同的選項,不同的選項用處也不一樣,要使的服務端能夠回調客戶端代碼,有以下幾種方式:
1. 配置服務多線程訪問
即設置ServiceBehavior屬性的ConcurrencyMode值爲Mutiple,這樣服務類就允許多線程訪問了,但是多線程訪問會代碼同步問題,所以需要處理好同步問題;
2. 回調重入
配置ServiceBehavior屬性的ConcurrencyMode值爲Reentrant,服務成爲單線程訪問,在回調發生時可衝入;
3. 單向回調
在回調接口中的操作契約中設置IsOneWay爲true,設置爲單向調用,也可進行回調
如果在使用回調時不遵循這3種方式,回調時會出現異常,無法完成回調工作。
二:客戶端處理回調的問題:
由於回調是服務端異步調用,因此會出現調用超時,跨線程訪問的問題,如果不認清這些問題的本質,就開始編寫代碼,往往回調會不成功的,如果使用控制檯工程,不使用UI,則跨線程訪問UI的問題不會出現,但是超時依然會出現。
1. 超時問題
如將
Console.WriteLine(name + ” 已加入”);
這行代碼改成
MessageBox.Show(name);
如果長時間不響應MessageBox,則會拋出異常,所以在回調處理中如果不標記IsOneWay爲true,則儘量不要在處理中採用耗時代碼,以免超時。
2. WinFrom中回調代碼訪問UI的問題
由於回調線程和UI的線程並非同一線程,因此在訪問UI時就會出現問題,要解決這一問題,和多線程訪問UI是一致的,將訪問代碼封送到UI線程中來處理,如使用Control.Invoke()函數,或者使用SynchronizationContext同步上下文來完成UI訪問任務。
3. WPF中訪問UIElement的問題
WPF已經不採用Windows消息循環系統了,而採用自行設計的消息系統,因此WPF中處理這種問題和WinForm並不相同,UIElement不在提供Invoke函數來封送代碼了,而且也沒有SynchronizationContext上下文,但是WPF提供了Dispatcher對象,通過Dispatcher.CurrentDispatcher來獲取當前線程(UI線程)的消息分發器對象,Dispatcher有一個Invoke函數,該函數可以實現將另一線程中的代碼封送到當前線程中來執行,這樣就可以解決跨線程訪問UI的問題。
三:安全問題
上面的代碼在單機中運行是沒有任何問題的,但是將客戶端服務端分到兩臺PC機中,問題就會出來了,無論調用服務也好回調也好,都會有安全問題,因此需要對安全問題進行配置,安全問題是一個大話題,這裏就不細說,爲了代碼能在多臺機器中運行,最簡單的就是不使用安全驗證,配置文件如下所示:
在配置文件中增加bindings節:
<bindings>
<netTcpBinding>
<binding name="TcpSecurity">
<security mode="None"> <!--不採用安全措施-->
<transport clientCredentialType="None" protectionLevel="None"/>
</security>
</binding>
</netTcpBinding>
</bindings>
這裏使用的是netTcpBinding, 如果使用別的綁定,則增加特定綁定的配置代碼,在終結點中增加bindingConfiguration屬性,並將值指向binding的名稱,如
<endpoint address="net.tcp://localhost:8000/chart"
binding="netTcpBinding"
contract="fzgk.IChart"
bindingConfiguration="TcpSecurity"
name="TcpBinding"/>
客戶端也一樣增加這些配置項,再次運行程序就不會出現安全問題了。