【WCF--初入江湖】05 WCF异步编程
05 WCF异步编程
一、服务设计最佳实践
在设计之初,是否用异步,应该由客户端来决定,而不应该去考虑服务的调用者调用的方式。
优点:充分利用多核CPU,
改善用户体验
缺点:滥用异步,会影响性能
二、实现异步的方式
WCF有两种方式实现异步调用代理类:
第一种:【方式1】用svcutil生成异步功能的代理类:
方式是在svcutil后添加参数 /async
svcutil /async http://localhost:5555/WCFService
【方式二】
右键服务引用--【配置服务引用】--在弹出框中,勾选【允许生成异步操作】
第二种: 在客户端修改服务接口,添加异步方法:
在服务端的服务接口是:
[ServiceContract] public interface IService1 { [OperationContract] byte[] GetFile(string fileName); }
则在客户端的项目中重新定义一个接口IAsyncService1,使其继承服务端的接口IService1,并额外添加BeginXXX()和EndXXX()方法,具体如下所示:
namespace Keasy5.WCF.Asyn.ByHand.Client { interface IAsyncService1 : IService1 { [OperationContract(AsyncPattern = true)] IAsyncResult BeginGetFile(string fileName, AsyncCallback callback, object asyncState); byte[] EndGetFile(IAsyncResult asyncResult); } }
三、实现方式
方式一:在添加服务引用时或后,修改服务引用配置
服务端的接口和实现类定义
IServer.cs
[ServiceContract] public interface IService1 { [OperationContract] string GetData(string message); }
Server.cs
public class Service1 : IService1 { public string GetData(string message) { System.Threading.Thread.Sleep(3000); return string.Format("You entered: {0}", message); } }
具体的做法是:
第一步:右键服务引用--【配置服务引用】--在弹出框中,勾选【允许生成异步操作】
第二步:客户端调用异步方法,有两种调用方式:
【方式一】客户端使用使用自动生成的代理类中的BeginXXX()和EndXXX()
private void button2_Click(object sender, EventArgs e) { Service1Client service1Client = new Service1Client(); service1Client.BeginGetData("自动生成的异步方法" ,doCallBack, service1Client); DoAfterCallWCFServer(); } private void doCallBack(IAsyncResult asyncResult) { Service1Client client = (Service1Client) asyncResult.AsyncState; string message = client.EndGetData(asyncResult); MessageBox.Show(message); } private Void DoAfterCallWCFServer() { MessageBox.Show("调用WCF后的后续操作!"); }
【方式二:使用xxxCompleted事件/xxxAsync方法】
private void button3_Click(object sender, EventArgs e) { Service1Client service1Client = new Service1Client(); service1Client.GetDataCompleted +=service1Client_GetDataCompleted; service1Client.GetDataAsync("XXXCompleted/XXXAsync"); //开始异步调用 DoAfterCallWCFServer(); } private void service1Client_GetDataCompleted(object sender, GetDataCompletedEventArgs e) { MessageBox.Show(e.Result); } private void DoAfterCallWCFServer() { MessageBox.Show("调用WCF后的后续操作!"); }
源代码下载:
链接: http://pan.baidu.com/s/1gd1PNO3 密码: qtel
方式二:在客户端修改服务接口,添加异步方法
在客户端修改的服务接口,添加异步方法,
在客户端决定采用异步方式调用的操作时,修改了客户端的服务契约接口,但并不影响服务端的契约定义,
第一步:创建3个项目:
【1】类库项目Keasy5.WCF.Asyn.Contract,用于定义客户端和服务端共同的契约
【2】WinForm项目Keasy5.WCF.Asyn.ByHand.Client,作为客户端;其引用类库项目Keasy5.WCF.Asyn.Contract
【3】WCF服务库项目Keasy5.WCF.Asyn.WCFServer,其引用类库项目Keasy5.WCF.Asyn.Contract
第二步:类库项目Keasy5.WCF.Asyn.ByHand.Client添加接口IServer:
IServer1.cs
using System; using System.Collections.Generic; using System.Linq; using System.ServiceModel; using System.Text; namespace Keasy5.WCF.Asyn.Contract { [ServiceContract] public interface IService1 { [OperationContract(Action = "", ReplyAction = "")] byte[] GetFile(string fileName); } }
第二步:WCF服务库项目Keasy5.WCF.Asyn.WCFServer添加一个类Server1,用于实现类库项目Keasy5.WCF.Asyn.Contract添加接口IServer1:
Server1.cs
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; using System.IO; using Keasy5.WCF.Asyn.Contract; namespace Keasy5.WCF.Asyn.WCFServer { public class Service1 : IService1 { public byte[] GetFile(string fileName) { string path = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, fileName); using (FileStream fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Read)) { byte[] bytes = new byte[fileStream.Length]; fileStream.Read(bytes, 0, (int)fileStream.Length); return bytes; } } } }
第三步:在客户端修改服务接口,添加异步方法
在Keasy5.WCF.Asyn.ByHand.Client项目中添加一个接口IAsyncService1接口,并且其继承类库项目Keasy5.WCF.Asyn.Contract定义的接口IServer1
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using Keasy5.WCF.Asyn.Contract; namespace Keasy5.WCF.Asyn.ByHand.Client { [ServiceContract] interface IAsyncService1 : IService1 { [OperationContract(AsyncPattern = true, Action = "", ReplyAction = "") ] IAsyncResult BeginGetFile(string fileName, AsyncCallback callback, object asyncState); byte[] EndGetFile(IAsyncResult asyncResult); } }
第四步:客户端调用异步方法:
【1】先在配置文件app.config中添加节点配置:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService1" /> </basicHttpBinding> </bindings> <client> <endpoint address="http://localhost:8733/Design_Time_Addresses/Keasy5.WCF.Asyn.WCFServer/Service1/" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
contract="Keasy5.WCF.Asyn.ByHand.Client.IAsyncService1"
name="basicHttpIService1" /> </client> </system.serviceModel> </configuration>
注意:节点的contract属性值是
Keasy5.WCF.Asyn.ByHand.Client.IAsyncService1
是客户端定义的接口IAsyncService1。
【2】然后用管道工厂ChannelFactory<T>,创建服务管道调用服务:
private void button1_Click(object sender, EventArgs e) { //使用通道工厂创建服务 ChannelFactory<IAsyncService1> channelFactory = new ChannelFactory<IAsyncService1>("basicHttpIService1"); IAsyncService1 asyncService1 = channelFactory.CreateChannel(); asyncService1.BeginGetFile(@"abc.jpg", DoGetFileCallBack, asyncService1); } private void DoGetFileCallBack(IAsyncResult asyncResult) { IAsyncService1 asyncService1 = (IAsyncService1) asyncResult.AsyncState; byte[] recievedBytes = asyncService1.EndGetFile(asyncResult); if (recievedBytes.Length > 0) { MemoryStream memoryStream = new MemoryStream(recievedBytes); this.pictureBox1.Image = Image.FromStream(memoryStream);
MessageBox.Show("异步调用服务的后续操作");
} else { MessageBox.Show("无法获取图片信息"); } }
注意:常见的异常和处理方法:
【1】当接口IAsyncService1没有添加[ServiceContract]特性,会出现如下异常:
在 ServiceModel 客户端配置部分中,找不到名称“basicHttpIService1”和
协定“Keasy5.WCF.Asyn.Contract.IService1”的终结点元素。
这可能是因为未找到应用程序的配置文件,
或者是因为客户端元素中找不到与此名称匹配的终结点元素。
解决方案:
在IAsyncService1没有添加[ServiceContract]特性
【2】异常:
类型“Keasy5.WCF.Asyn.Contract.IService1”中的同步 OperationContract 方法“GetFile”与异步 OperationContract 方法“BeginGetFile”和“EndGetFile”匹配,
因为它们具有相同的操作名称“GetFile”。
当同步 OperationContract 方法与一对异步 OperationContract 方法匹配时,
这两个 OperationContracts 必须具有相同的“Action”属性值。
在此情况下,值是不同的。要解决此问题,请更改其中一个 OperationContracts 的“Action”属性以与另一个匹配。此外,更改其中一个方法的名称将阻止匹配。
解决方法:
在
IServer1的GetFile方法和
IAsyncSever1的BeginGetFile方法
都添加如下特性:Action = "", ReplyAction = ""
[OperationContract(Action = "", ReplyAction = "")]
使它们的方法签名一样。
【3】异常:
在 ServiceModel 客户端配置部分中,找不到名称“basicHttpIService1”和
协定“Keasy5.WCF.Asyn.ByHand.Client.IAsyncService1”的终结点元素。
这可能是因为未找到应用程序的配置文件,或者是因为客户端元素中找不到与此名称匹配的终结点元素。
这是由于客户端配置文件端点(endpoint )的Contract值是服务端的接口类型IServer1,而不是客户端的接口类型IAsyncServer1;
错误的客户端配置:
<client> <endpoint address="http://localhost:8733/Design_Time_Addresses/Keasy5.WCF.Asyn.WCFServer/Service1/" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1" contract="MyWCFService.IService1" name="basicHttpIService1" /> </client>
应该修改为客户端的接口类型IAsyncServer1:contract="Keasy5.WCF.Asyn.ByHand.Client.IAsyncService1",下面是正确的配置:
<client> <endpoint address="http://localhost:8733/Design_Time_Addresses/Keasy5.WCF.Asyn.WCFServer/Service1/" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1" contract="Keasy5.WCF.Asyn.ByHand.Client.IAsyncService1" name="basicHttpIService1" /> </client>
源码下载:
链接: http://pan.baidu.com/s/1c0nIOLi%20 密码: d71x