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客户端异步调用服务实例:

  • 工程结构如下图所示:

  

  • 工程结构说明:
  1. 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事件里,通过参数类型GetInfoCompletedEventArgsResult可以获得返回结果。而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,关于这一部分内容,我将在以后的博文中做解读。
时间: 2024-10-11 14:27:05

WCF初探-11:WCF客户端异步调用服务的相关文章

WCF系列教程之客户端异步调用服务

本文参考自http://www.cnblogs.com/wangweimutou/p/4409227.html,纯属读书笔记,加深记忆 一.简介 在前面的随笔中,详细的介绍了WCF客户端服务的调用方法,但是那些操作全都是同步的,所以我们需要很长的时间等待服务器的反馈,如何一台服务器的速度很慢,所以客户端得到结果就需要很长的时间,试想一下,如果客户端是个web项目,那么客户体验可想而知,所以为了不影响后续代码执行和用户的体验,就需要使用异步的方式来调用服务.注意这里的异步是完全针对客户端而言的,与

Webservice客户端动态调用服务端功能方法

一.发布WebService服务 方式一:在服务端生成wsdl文件,下方客户端直接引用即可 优点:针对要发布的方法生成一个wsdl文件即可,无需多余配置. 缺点:每次服务端方法发生改变都需要重新生成相应的wsdl文件,不适合于功能需要经常变动的方法. 方式二: 二.Client调用服务端接口  1. 项目架构如下: 2.调用过程: 1)右键点击项目名称-->"NEW"-->"Other"-->输入WEB service client 如下 2)完成

WCF初探-15:WCF操作协定

前言: 在前面的文章中,我们定义服务协定时,在它的操作方法上都会加上OperationContract特性,此特性属于OperationContractAttribute 类,将OperationContract应用于方法,以指示该方法实现作为服务协定(由 ServiceContractAttribute 属性指定)一部分的服务操作.OperationContractAttribute 属性声明方法是服务协定中的操作. 只有具有 OperationContractAttribute 属性的方法可

WCF初探-10:WCF客户端调用服务

创建WCF 服务客户端应用程序需要执行下列步骤: 获取服务终结点的服务协定.绑定以及地址信息 使用该信息创建 WCF 客户端 调用操作 关闭该 WCF 客户端对象 WCF客户端调用服务存在以下特点: 服务和客户端使用托管属性.接口和方法对协定进行建模. 若要连接客户端应用程序中的服务,则需要获取该服务协定的类型信息.通常,我们使用Svcutil.exe(ServiceModel Metadata Utility Tool)来完成,也可以直接在客户端项目上引用服务地址完成.它们会从服务中下载元数据

【WCF系列】(四)WCF客户端怎么消费服务

WCF客户端怎么消费服务 获取服务绑定协议.绑定和地址:实现方式 SvcUtil方式:SvcUtil.exe是一个命令行工具,位于:C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin可以将SvcUtil.exe添加到VS中方便以后的运用 生成代理类: svcutil net.tcp://192.168.0.100:3333/ChatService /language:C# /out:proxy.cs /config:app.config

客户端的异步调用

C#客户端的异步操作 阅读目录 开始 示例项目介绍 同步调用服务 异步接口介绍 1. 委托异步调用 2. 使用IAsyncResult接口实现异步调用 3. 基于事件的异步调用模式 4. 创建新线程的异步方式 5. 使用线程池的异步方式 6. 使用BackgroundWorker实现异步调用 客户端的其它代码 各种异步方式的优缺点 异步文件I/O操作 数据库的异步操作 异步设计的使用总结 在Asp.net中使用异步 上篇博客[用Asp.net写自己的服务框架] 我讲述了如何实现自己的服务框架,但

【WCF全析(一)】--服务协定及消息模式

上周微软开发布会说.NET支持完全跨平台和并开放Core源码的新闻,让我们顿时感到.NET要迎来它的春天.虽然早在几年前.NET就能开发Android和IOS,但是这次的跨平台把Linux都放到了微软战略之中,以后的.NET Developer就可以使用Vs开发Linux应用了,Developer又有了新的选择,从微软的战略转型也可以看出互联网已经步入到了新的模式,以后不再是PC的时代,移动互联和云时代已经到来. 最近做项目时使用到了WCF,项目把数据层和程序层进行了分割,相互之间的数据传输使用

【架构之路之WCF全析(一)】--服务协定及消息模式

上周微软开发布会说.NET支持完全跨平台和并开放Core源码的新闻,让我们顿时感到.NET要迎来它的春天.虽然早在几年前.NET就能开发Android和IOS,但是这次的跨平台把Linux都放到了微软战略之中,以后的.NET Developer就可以使用Vs开发Linux应用了,Developer又有了新的选择,从微软的战略转型也可以看出互联网已经步入到了新的模式,以后不再是PC的时代,移动互联和云时代已经到来. 最近做项目时使用到了WCF,项目把数据层和程序层进行了分割,相互之间的数据传输使用

CXF简单示例(二)之异步调用

在实际使用中,客户端在调用服务时,并不能及时得到响应,比如调用的服务本身是一个耗时费事的活,服务器破死命的跑,也只能在一分钟后才能够返回结果,这时候如果是同步的情况,那么客户端就必须在这里到等上一分钟,啥事不干.这肯定是不能容忍的,光吃饭不干活,再有这么好的事,请告诉我! OK,所以我们需要异步调用. 在这里,先介绍CXF提供的一个工具:WSDL2Java. 我们可以方便的利用它来生成相应的客户端接口,配置什么的脏活累活它都给我们干了,而我们只需要这样直接用就行了. 1. 获得服务的WSDL路径