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)]
IAsyncResultBeginQueryWeather(string city, AsyncCallback callback, object asyncState);
string EndQueryWeather(IAsyncResult result);
}
可以看到,和同步方式的契約聲明不同的是:
-
在接口名稱前加了一個Begin
-
設置了 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);
}
和同步方式的契約聲明也有兩點區別:
-
返回值不是string,而是Task<string>
-
接口名稱中加了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架構中,服務器端不感知客戶端以同步還是異步的方式來訪問,客戶端也感知不到服務器端是同步還是異步方式來實現服務的,這個也是一種松耦合的體現。
至於實現異步操作的實現:
-
客戶端直接通過代碼生成器可以同時生成異步步訪問的接口,無需自己編寫代碼。
-
服務器端可以通過async和Task來簡化異步接口的實現。