WCF入門(二)——異步操作


WCF是網絡編程,既然是網絡編程,那么就一般離不開異步操作了。異步操作按照發生源可以分為客戶端異步和服務器端異步,本文就分別簡單的介紹一下該如何實現。

一、客戶端異步

以一個簡單的服務為例:

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        string Foo();
    }

這個服務不用輸入,僅僅返回一個字符串,通常的時候,我們喜歡和調用本地函數方式類似的以調用WCF服務,並且同步等待至遠程返回結果。

    var client = new WcfClient.Service.Service1Client();
    var result = client.Foo();
    Console.WriteLine(result);

一般來說,這個並沒有什么問題;但是,和調用本地函數一樣,如果該函數需要將長時間才能返回結果,則會阻塞線程,如果調用線程是UI線程的話則會導致UI沒有響應。特別是像WCF這種遠程調用的方式更會由於網絡原因等導致耗時較長。

按照本地耗時函數的處理方式,我們可以借助Task來處理這個等待,然后獲取返回值后,再通知界面。

    var client = new WcfClient.Service.Service1Client();
    var result = await Task.Run(() => client.Foo());
    Console.WriteLine(result);

這個方式雖然不阻塞界面,但是消耗了一個Task在同步等待,效率不高。實際上,對於這種網絡操作,VisualStudio在生成本地客戶端代碼的時候,就生成了異步方式訪問的接口。

  

直接使用這個異步接口,就可以實現IO異步的方式訪問,不用新開啟一個Task來同步等待。

    var client = new WcfClient.Service.Service1Client();
    var result = await client.FooAsync();
    Console.WriteLine(result);

關於默認的異步訪問接口,也可以在根據服務生成本地代碼的時候選擇使用傳統的BeginFoo,EndFoo的那種形式(由於遠不如Task式的異步訪問好用,不推薦)。

 

二、服務器端異步

服務器端異步指的主要是服務的異步實現,

以一個天氣預報的WCF服務為例,假如我們要實現一個這樣的接口:

    public interface IService1
    {
        string QueryWeather(string city);
    }

由於我本地並沒有天氣預報的數據,要實現一個這樣的服務,必須得到氣象網站去查詢,按照傳統的同步方式,則實現如下:

    public class Service1 : IService1
    {
        public string QueryWeather(string city)
        {
            var queryTask = QueryWeatherFromRemoteSite(city);
            queryTask.Wait();
            return queryTask.Result;
        }

        static Task<string> QueryWeatherFromRemoteSite(string city)
        {
            //
省略實現
        }
    }

 很明顯,每次一次處理QueryWeather的時候,都得阻塞當前線程至遠程查詢的完成,極大的浪費了系統資源。需要改成異步的方式實現。

傳統的異步實現

在.Net 4.5前,要異步實現QueryWeather函數,必須得把接口聲明成如下形式:

    [ServiceContract]
    public interface IService1
    {
        [OperationContract(
AsyncPattern = true)]
        IAsyncResult
BeginQueryWeather(string city, AsyncCallback callback, object asyncState);
        string EndQueryWeather(IAsyncResult result);
    }

可以看到,和同步方式的契約聲明不同的是:

  1. 在接口名稱前加了一個Begin
  2. 設置了 AsyncPattern = true

然后就要實現這個接口了,如下是一個簡單的示例:

    public class Service1 : IService1
    {
        #region IService1
成員

        public IAsyncResult BeginQueryWeather(string city, AsyncCallback callback, object asyncState)
        {
            return new CompletedAsyncResult<string>(QueryWeatherFromRemoteSite(city));
        }

        public string EndQueryWeather(IAsyncResult result)
        {
            return result.AsyncState as string;
        }

        #endregion

        static Task<string> QueryWeatherFromRemoteSite(string city)
        {
            //
這里只是一個樁函數,表示的是一個異步方法
            return Task.FromResult("
");
        }
    }

    class CompletedAsyncResult<T> : IasyncResult
    {
        Task<T> task;
        ManualResetEvent waitHandle = new ManualResetEvent(false);

        public CompletedAsyncResult(Task<T> task)
        {
            this.task = task;
            task.ContinueWith(_ => waitHandle.Set());
        }

        #region IAsyncResult Members

        public object AsyncState { get { return task.Result; } }
        public WaitHandle AsyncWaitHandle { get { return waitHandle; } }
        public bool CompletedSynchronously { get { return true; } }
        public bool IsCompleted { get { return task.IsCompleted; } }

        #endregion
    }

編譯運行后,從TestClient上來看,生成的接口並沒有帶上前面的Begin,從其發布的Wsdl來看,和之前的同步實現沒有區別,也就是說:客戶端是感知不到服務器端是如何實現的

  

更加簡單的異步實現:

從上面的實現來看,實現異步操作是要實現兩個接口,BeginXXX和EndXXX,這個是非常麻煩的,在.Net 4.5里,我們可以通過async語法糖來簡化這一過程。

首先還是來看看接口聲明:

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        Task<string> QueryWeatherAsync(string city);
    }

和同步方式的契約聲明也有兩點區別:

  1. 返回值不是string,而是Task<string>
  2. 接口名稱中加了Async后綴(這個其實不是必要的,但是為了良好的編程習慣,建議加上)。

既然接口就定義除了Task類型的返回值,那么實現就簡單了:

    public class Service1 : IService1
    {
        #region IService1
成員

        public Task<string> QueryWeatherAsync(string city)
        {
            return QueryWeatherFromRemoteSite(city);
        }

        #endregion

        static Task<string> QueryWeatherFromRemoteSite(string city)
        {
            //
這里只是一個樁函數,表示的是一個異步方法
            return Task.FromResult("
");
        }
    }

比前面的BeginXXX的方式來說,簡單且簡潔了不少。

編譯運行后,從TestClient上來看,生成的接口並沒有帶上結尾的Async,非常人性化。

  

 

三、小結:

在WCF架構中,服務器端不感知客戶端以同步還是異步的方式來訪問,客戶端也感知不到服務器端是同步還是異步方式來實現服務的,這個也是一種松耦合的體現。

至於實現異步操作的實現:

  1. 客戶端直接通過代碼生成器可以同時生成異步步訪問的接口,無需自己編寫代碼。
  2. 服務器端可以通過async和Task來簡化異步接口的實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM