• 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; }
        }
    }
    View Code

      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();
                }
            }
        }
    }
    View Code

      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();
                }
            }
        }
    }
    View Code

      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>
    View Code

      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());
                }
            }
        }
    }
    View Code

      从上面的代码可以看出客户端代理类调用采用了事件驱动机制,服务的方法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,关于这一部分内容,我将在以后的博文中做解读。
  • 相关阅读:
    Mix 10 上的asp.net mvc 2的相关Session
    Vista、XP SP2主流支持即将终止
    向Visual Studio 2010迁移的电子书
    ASP.NET MVC 2 转换工具
    Javascript瘦身工具AJAX Minifier
    微软公司的安全开发周期模型
    User Experience Kit
    乐在其中设计模式(C#) 迭代器模式(Iterator Pattern)
    [翻译]使用ASP.NET AJAX让GridView的数据行显示提示框(ToolTip)
    [翻译]使用ASP.NET AJAX实现幻灯片效果
  • 原文地址:https://www.cnblogs.com/linybo/p/13305907.html
Copyright © 2020-2023  润新知