正文
WCF初探-11:WCF客户端异步调用服务
小程序:扫一扫查出行
【扫一扫了解最新限行尾号】
复制小程序
【扫一扫了解最新限行尾号】
复制小程序
前言:
- 在上一篇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(); List<User> Users = new List<User>();
Users.Add(new User { ID = , Name = "JACK", Age = , Nationality = "CHINA" });
Users.Add(new User { ID = , Name = "TOM", Age = , Nationality = "JAPAN" });
Users.Add(new User { ID = , Name = "SMITH", Age = , Nationality = "KOREA" });
Users.Add(new User { ID = , Name = "ALENCE", Age = , Nationality = "INDIA" });
Users.Add(new User { ID = , Name = "JOHN", Age = , 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 = ; 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 = ; 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,关于这一部分内容,我将在以后的博文中做解读。