前言:
- 在上一篇WCF初探-10:WCF客戶端調用服務 中,我詳細介紹了WCF客戶端調用服務的方法,但是,這些操作都是同步進行的。有時我們需要長時間處理應用程序並得到返回結果,但又不想影響程序后面代碼部分的執行,這時我們就需要考慮使用異步的方式來調用服務。注意這里的異步是完全針對客戶端而言的,與WCF服務契約的方法是否異步無關,也就是在不改變操作契約的情況下,我們可以用同步或者異步的方式調用WCF服務。
WCF客戶端異步調用服務方式:
- 通過代理類異步調用服務。就需要通過使用事件驅動的異步調用模型,客戶端可以對此接口異步調用操作。基於事件的異步模型設計准則規定,如果返回了多個值,則一個值會作為 Result 屬性返回,其他值會作為 EventArgs 對象上的屬性返回。 因此產生的結果之一是,如果客戶端使用基於事件的異步命令選項導入元數據,且該操作返回多個值,則默認的 EventArgs 對象返回一個值作為 Result 屬性,返回的其余值是 EventArgs 對象的屬性。如果要將消息對象作為 Result 屬性來接收並要使返回的值作為該對象上的屬性,請使用 /messageContract 命令選項。 這會生成一個簽名,該簽名會將響應消息作為 EventArgs 對象上的 Result 屬性返回。 然后,所有內部返回值就都是響應消息對象的屬性了。
- 使用通道工廠以異步方式調用操作。使用 ChannelFactory<TChannel> 時,不支持事件驅動的異步調用模型。這時我們需要異步重寫服務契約的接口,每個方法都包含一組beginXXX和endXXX,XXX代表方法名。並且我們需要將異步操作的方法上的 AsyncPattern 屬性設置為 true。前一個方法啟動調用,而后一個方法在操作完成時檢索結果。
WCF客戶端異步調用服務示例:
- 工程結構如下圖所示:
- 工程結構說明:
- Service:類庫程序,定義服務契約和實現,里面包含User數據契約和GetInfo()獲取用戶信息的服務契約方法。
IUserInfo.cs的代碼如下:

using System.ServiceModel; using System.Collections.Generic; using System.Runtime.Serialization; namespace Service { [ServiceContract] public interface IUserInfo { [OperationContract] User[] GetInfo(int? id=null); } [DataContract] public class User { [DataMember] public int ID { get; set; } [DataMember] public string Name { get; set; } [DataMember] public int Age { get; set; } [DataMember] public string Nationality { get; set; } } }
UserInfo.cs的代碼如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace Service { public class UserInfo:IUserInfo { public User[] GetInfo(int? id=null) { Thread.Sleep(1000); List<User> Users = new List<User>(); Users.Add(new User { ID = 1, Name = "JACK", Age = 20, Nationality = "CHINA" }); Users.Add(new User { ID = 2, Name = "TOM", Age = 18, Nationality = "JAPAN" }); Users.Add(new User { ID = 3, Name = "SMITH", Age = 22, Nationality = "KOREA" }); Users.Add(new User { ID = 4, Name = "ALENCE", Age = 21, Nationality = "INDIA" }); Users.Add(new User { ID = 5, Name = "JOHN", Age = 22, Nationality = "SINGAPORE" }); if (id != null) { return Users.Where(x => x.ID == id).ToArray(); } else { return Users.ToArray(); } } } }
2. Host:控制台應用程序,添加對Service程序集的引用,寄宿服務程序。
Program.cs代碼如下

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Service; using System.ServiceModel; namespace Host { class Program { static void Main(string[] args) { using (ServiceHost host = new ServiceHost(typeof(UserInfo))) { host.Opened += delegate { Console.WriteLine("服務已經啟動,按任意鍵終止!"); }; host.Open(); Console.Read(); } } } }
App.config代碼如下:

<?xml version="1.0"?> <configuration> <system.serviceModel> <services> <service name="Service.UserInfo" behaviorConfiguration="mexBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:1234/UserInfo/"/> </baseAddresses> </host> <endpoint address="" binding="wsHttpBinding" contract="Service.IUserInfo" /> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> </service> </services> <behaviors> <serviceBehaviors> <behavior name="mexBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
3. Client1:控制台應用程序。添加對服務終結點地址http://localhost:1234/UserInfo/的引用,設置服務命名空間為UserInfoServiceRef,點擊高級設置,勾選生成異步操作選項,
生成客戶端代理類和配置文件代碼后,完成Client1對服務的調用。
Program.cs的代碼如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Client1.UserInfoServiceRef; namespace Client1 { class Program { static void Main(string[] args) { UserInfoClient proxy = new UserInfoClient(); proxy.GetInfoCompleted += new EventHandler<GetInfoCompletedEventArgs>(proxy_GetInfoCompleted); proxy.GetInfoAsync(null); Console.WriteLine("此字符串在調用方法前輸出,說明異步調用成功!"); Console.Read(); } static void proxy_GetInfoCompleted(object sender, GetInfoCompletedEventArgs e) { User[] Users = e.Result.ToArray(); Console.WriteLine("{0,-10}{1,-10}{2,-10}{3,-10}", "ID", "Name", "Age", "Nationality"); for (int i = 0; i < Users.Length; i++) { Console.WriteLine("{0,-10}{1,-10}{2,-10}{3,-10}", Users[i].ID.ToString(), Users[i].Name.ToString(), Users[i].Age.ToString(), Users[i].Nationality.ToString()); } } } }
從上面的代碼可以看出客戶端代理類調用采用了事件驅動機制,服務的方法GetInfo()與基於事件的異步調用方法一起使用且形式為GetInfoCompleted 的操作完成事件。客戶端具體實現在proxy_GetInfoCompleted事件里,通過參數類型GetInfoCompletedEventArgs的Result可以獲得返回結果。而proxy.GetInfoAsync(null)代碼說明服務開始異步調用。在此客戶端我特地輸出了Console.WriteLine("此字符串在調用方法前輸出,說明異步調用成功!")一串文字來證明服務是異步調用的。因為在操作契約GetInfo()的方法中,我讓程序線程休眠了1s,模擬程序執行的時間。如果客戶端調用服務異步執行,我們應該會看到字符串文字應該顯示在調用結果的前面。程序結果顯示如下,說明結果調用成功。
4. Client2: 控制台應用程序。此客戶端我們是通過svcutil.exe工具生成的客戶端代理類。在命令行中輸入以下圖中的命令,將生成的UserInfoClient.cs和App.config
復制到Client2的工程目錄下,Program.cs的代碼實現和Client1中的一樣。
5. Client3: 控制台應用程序。在此客戶端中,我們將通過ChannelFactory<TChannel>的方式對服務進行異步調用。想一想:我們如果對服務契約進行程序集引用,
可是我們的服務契約並沒有異步服務方法的定義,那我們怎么來調用服務方法呢?所以使用ChannelFactory<TChannel>的方式對服務進行異步調用就要比同步的
方式多一個步驟,那就是我們必須在客戶端對服務契約的方法進行重寫,而不是對服務契約程序集的引用,就像代理方式一樣。代碼參考如下:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")] [System.ServiceModel.ServiceContractAttribute(ConfigurationName="IUserInfo")] public interface IUserInfo { [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IUserInfo/GetInfo", ReplyAction="http://tempuri.org/IUserInfo/GetInfoResponse")] Service.User[] GetInfo(System.Nullable<int> id); [System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://tempuri.org/IUserInfo/GetInfo", ReplyAction="http://tempuri.org/IUserInfo/GetInfoResponse")] System.IAsyncResult BeginGetInfo(System.Nullable<int> id, System.AsyncCallback callback, object asyncState); Service.User[] EndGetInfo(System.IAsyncResult result); }
這樣我可以對服務的操作方法進行異步調用了,但是ChannelFactory<TChannel>的方式不支持事件驅動模型,所以我們可以利用回調函數來對其服務方法進行調用。
接下來,我們完成Client3的代碼操作。
第一步:利用svcutil.exe生成客戶端類,我們輸入一下命令,將生成的文件復制到Client3的工程目錄下。
第二步:實現Client3的Program.cs的代碼。代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Channels; using System.Runtime.Serialization; using Service; namespace Client3 { class Program { static void Main(string[] args) { EndpointAddress address = new EndpointAddress("http://localhost:1234/UserInfo"); WSHttpBinding binding = new WSHttpBinding(); ChannelFactory<IUserInfo> factory = new ChannelFactory<IUserInfo>(binding, address); IUserInfo channel = factory.CreateChannel(); IAsyncResult ar = channel.BeginGetInfo(null, GetInfoCallback, channel); Console.WriteLine("此字符串在調用方法前輸出,說明異步調用成功!"); Console.Read(); } static void GetInfoCallback(IAsyncResult ar) { IUserInfo m_service = ar.AsyncState as IUserInfo; User[] Users = m_service.EndGetInfo(ar); Console.WriteLine("{0,-10}{1,-10}{2,-10}{3,-10}", "ID", "Name", "Age", "Nationality"); for (int i = 0; i < Users.Length; i++) { Console.WriteLine("{0,-10}{1,-10}{2,-10}{3,-10}", Users[i].ID.ToString(), Users[i].Name.ToString(), Users[i].Age.ToString(), Users[i].Nationality.ToString()); } } } }
- 總結:我們可以看到客戶端異步調用服務的方法和同步的差不多,只是實現的機制不一樣。如果讀者想要更好的理解代碼,我建議讀者去了解一下IAsyncResult,關於這一部分內容,我將在以后的博文中做解讀。