WCF中的異步調用

WCF Tips之四

WCF與Web Service不一樣的是,當咱們定義了服務契約的操做時,不論是經過ChannelFactory建立服務代理對象,仍是經過SvcUtil的默認方式生成服務代理對象,客戶端在調用這些代理對象時,都沒法直接實現異步方式的調用。例如,對於以下的服務操做定義:
[OperationContract]
Stream TransferDocument(Document document);

在調用代理對象的方法時,咱們沒法找到對應於TransferDocument()操做的BeginTransferDocument()和EndTransferDocument()異步方法。

這樣的設計使得咱們沒法經過編程方式異步地調用服務的操做,除非咱們在定義服務接口時,直接加入相關操做的異步方法。然而,這又直接致使了服務的設計與方法調用方式之間的耦合。一個好的框架設計要素在於,無論客戶端的調用方式(同步或者異步),服務的設計與實現應該是一致的。對於服務的設計者而言,在設計之初,就不該該去考慮服務的調用者調用的方式。換言之,服務操做到底是否採用異步方式,應該由客戶端的調用者決定。所以,全部與異步調用相關的內容應該只與客戶端相關。WCF遵循了這一規則。

在我編寫的應用程序中,會暴露一個傳送文檔文件的服務操做。我並不知道也並不關心調用該操做的客戶端是否採用異步方式。所以,如上所述的服務操做定義是徹底正確的。

那麼,客戶端究竟應該如何執行異步調用呢?若是採用編程方式得到服務代理對象,這一問題會變得比較糟糕。由於我將服務契約的定義單獨造成了一個程序集,並在客戶端直接引用了它。然而,在這樣的服務契約程序集中,是沒有包含異步方法的定義的。所以,我須要修改在客戶端的服務定義,增長操做的異步方法。這無疑爲服務契約的重用帶來障礙。至少,咱們須要在客戶端維持一份具備異步方法的服務契約。

所幸,在客戶端決定採用異步方式調用我所設計的服務操做時,雖然須要修改客戶端的服務契約接口,但並不會影響服務端的契約定義。所以,服務端的契約定義能夠保持不變,而在客戶端則修改接口定義以下:
    [ServiceContract]
    
public   interface  IDocumentsExplorerService
    {
        [OperationContract]
        Stream TransferDocument(Document document);

        [OperationContract(AsyncPattern 
=   true )]
        IAsyncResult BeginTransferDocument(Document document,
                                             AsyncCallback callback, 
object  asyncState);

        Stream EndTransferDocument(IAsyncResult result);
    } 

注意,在 BeginTransferDocument()方法上,必須在OperationContractAttribute中將AsyncPattern屬性值設置爲true,由於它的默認值爲false。

調用方式以下:
BasicHttpBinding binding  =   new  BasicHttpBinding();
binding.SendTimeout 
=  TimeSpan.FromMinutes( 10 );
binding.TransferMode 
=  TransferMode.Streamed;
binding.MaxReceivedMessageSize 
=   9223372036854775807 ;

EndpointAddress address 
=   new  EndpointAddress
    (
" http://localhost:8008/DocumentExplorerService " );

ChannelFactory
< IDocumentsExplorerService >  factory  =
    new
 ChannelFactory < IDocumentsExplorerService > (binding,address);

m_service 
=  factory.CreateChannel();

……
IAsyncResult result 
=  m_service.BeginTransferDocument(doc, null , null );
result.AsyncWaitHandle.WaitOne();
Stream stream 
=  m_service.EndTransferDocument(result);

若是採用SvcUtil生成客戶端代理文件,能夠有更好的方式實現異步,也就是使用SvcUtil的/async開關,例如:
svcutil  / async http: // localhost:8008/DocumentExplorerService

惟一不足的是,它會不分青紅皁白,爲全部服務操做都生成對應的異步方法。這樣的作法未免過於武斷。

合理地利用服務的異步調用,能夠有效地提升系統性能,合理分配任務的執行。特別對於UI應用程序而言,能夠提升UI的響應速度,改善用戶體驗。在我編寫的應用程序中,下載的文件若是很大,就有必要採用異步方式。

對於異步調用的完成,雖然WCF提供了諸如阻塞、 等待 輪詢 等機制,但最好的方式仍是使用回調。也就是利用Begin方法參數中的AsyncCallback對象。這是一個委託對象,它的定義以下所示:
public   delegate   void  AsyncCallback(IAsyncResult ar);

利用異步方式執行服務操做,使得服務在執行過程當中不會阻塞主線程,當方法執行完成後,經過AsyncCallback回調對應的方法,能夠通知客戶端服務執行完畢。例如:
// Invoke it Asynchronously
m_service.BeginTransferDocument(m_doc,OnTransferCompleted, null );

// Do some work;


// callback method
void  OnTransferCompleted(IAsyncResult result)
{
    Stream stream 
=  m_service.EndTransferDocument(result);
    result.AsyncWaitHandle.Close();

    lbMessage.Text 
=   string .Format( " The file {0} had been transfered sucessfully. " ,
                                       m_doc.FileName);
}

在調用 BeginTransferDocument()方法以後,主線程不會被阻塞,仍然能夠繼續執行其它工做。而當服務方法執行完畢以後,會自動調用回調方法,執行方法中的內容。

上述實現存在一個問題,就是對於lbMessage控件的操做。因爲回調方法並不是運行在主線程中,若是回調方法須要更新與異步調用結果相關的界面,例如本例中的lbMessage控件,則須要將回調的調用封送(Marshal)到當前主程序界面的同步上下文中。咱們可使用SynchronizationContext以及它的SendOrPostCallback委託,對調用進行封送:
public  ExplorerClientForm()
{
    InitializeComponent();
    m_synchronizationContext 
=  SynchronizationContext.Current;
}

private  SynchronizationContext m_synchronizationContext;

則回調方法修改成:
// callback method
void  OnTransferCompleted(IAsyncResult result)
{
    Stream stream 
=  m_service.EndTransferDocument(result);
    result.AsyncWaitHandle.Close();

    SendOrPostCallback callback 
=   delegate
    {
        lbMessage.Text 
=   string .Format( " The file {0} had been transfered sucessfully. " ,
                                          m_doc.FileName);
    };
    m_synchronizationContext.Send(callback,
null );
}

在調用異步方法時,因爲對Begin TransferDocument() 和End TransferDocument() 方法的調用可能會在不一樣的方法體中,於是我將服務代理對象定義爲private字段。若是但願將服務對象定義爲一個局部變量,能夠在調用 Begin TransferDocument()方法時,將代理對象傳遞到方法的asyncState參數中,而後在調用 End TransferDocument()方法以前,經過IAsyncResult得到準確的服務代理對象:
m_service.BeginTransferDocument(m_doc,OnTransferCompleted,m_service);

將m_service做爲asyncState對象傳入以後,在調用 End TransferDocument()方法以前,就能夠根據它先得到服務代理對象:
IDocumentsExplorerService m_service  =  result.AsyncState  as  IDocumentsExplorerService;
Stream stream 
=  m_service.EndTransferDocument(result);
// rest codes
相關文章
相關標籤/搜索