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,關於這一部分內容,我將在以后的博文中做解讀。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM