65.9K
CodeProject 正在变化。 阅读更多。
Home

如何同步和异步调用 WCF 服务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (48投票s)

2010 年 7 月 3 日

CPOL

9分钟阅读

viewsIcon

304694

downloadIcon

9137

本文将通过一个示例向您展示如何同步和异步调用 WCF 服务。

引言

本文将通过一个示例向您展示如何同步和异步调用 WCF 服务。

背景

Windows Communication Foundation (WCF) 是 .NET Framework 中的一个应用程序编程接口,用于构建互联的面向服务的应用程序。 WCF 客户端有两种方式访问 WCF 服务提供的功能。它们是同步和异步 WCF 调用。

本文将通过一个示例向您展示如何以同步和异步两种方式访问 WCF 服务。本文假设读者对 WCF 服务有基本的了解。如果您不熟悉 WCF 服务,此链接是一个很好的入门资源。在本文中,我将首先构建一个 WCF 服务。然后我将向您展示如何访问此服务。以下是此示例 Visual Studio 解决方案在解决方案资源管理器中的屏幕截图:

VisualStudio.jpg

此 Visual Studio 解决方案包含三个 .NET 项目:

  • WCFServiceImplementation”项目是一个实现 WCF 服务的类库。
  • WCFServiceHost”项目是一个 ASP.NET Web 应用程序,它托管在“WCFServiceImplementation”项目中实现的 WCF 服务。
  • WCFCallExample”项目是一个 WPF 应用程序,我将在此演示访问 WCF 服务的两种方式。

此 Visual Studio 解决方案是在 Visual Studio 2008 中使用 C# 开发的。我们将首先了解 WCF 服务是如何实现的。

WCF 服务的实现

VisualStudioImplementation.jpg

.NET 项目“WCFServiceImplementation”是一个类库。整个 WCF 服务在该项目的“StudentService.cs”文件中实现:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
 
namespace WCFServiceImplementation
{
    [ServiceContract]
    public interface IStudentService
    {
        [OperationContract]
        List<Student> GetStudents(int NoOfStudents);
    }
 
    [DataContract]
    public class Student
    {
 
        [DataMember(Order = 1)]
        public int StudentID { get; set; }
 
        [DataMember(Order = 2)]
        public string StudentName { get; set; }
 
        [DataMember(Order = 3)]
        public int Score { get; set; }
 
        [DataMember(Order = 4)]
        public DateTime EvaluationTime { get; set; }
    }
 
    public class StudentService : IStudentService
    {
        public List<Student> GetStudents(int NoOfRecords)
        {
            List<Student> studentList = new List<Student>();
 
            Random rd = new Random();
            for (int Idex = 1; Idex <= NoOfRecords; Idex++)
            {
                Student student = new Student();
                student.StudentID = Idex;
                student.StudentName = "Student Name No." + Idex.ToString();
                student.Score = (int)(60 + rd.NextDouble() * 40);
                student.EvaluationTime = System.DateTime.Now;
 
                studentList.Add(student);
            }
 
            System.Threading.Thread.Sleep(10000);
 
            return studentList;
        }
    }
}

此类库定义了以下内容:

  • 一个“数据契约” “Student”,它是一个用于存储单个学生信息的类。
  • 一个“服务契约” “接口” “IStudentService”。此“接口”定义了 WCF 服务应该向客户端公开哪些功能。在此示例中,它公开了一个“操作契约” “GetStudents”。

服务契约” “IStudentService”在“StudentService”类中实现。访问 WCF 服务时,WCF 客户端可以通过向客户端的代理方法传递学生记录的数量来调用“GetStudents”方法。“GetStudents”方法然后随机生成一个“Student”对象的列表,并将这些对象的列表发送给调用者。您可能会注意到,为了人为地延迟 WCF 服务的响应,以便我们可以在客户端看到同步和异步 WCF 调用之间的区别,“System.Threading.Thread.Sleep(10000)”被添加到“GetStudents”方法中。

WCF 服务宿主应用程序“WCFServiceHost”

WCF 服务托管在 ASP.NET 应用程序项目“WCFServiceHost”中,在解决方案资源管理器中显示如下:

VisualStudioHost.jpg

在该项目中,WCF 服务托管在“StudentService.svc”文件中。由于 WCF 服务的整个实现已在“WCFServiceImplementation”类库中完成,因此“StudentService.svc”文件非常简单,如下所示:

 <%@ ServiceHost Language="C#" Debug="true" 
    Service="WCFServiceImplementation.StudentService" %>

Web.config”文件中的 WCF 服务配置如下:

<system.serviceModel>
    <services>
      <service behaviorConfiguration="WCFServiceHost.ServiceBehavior"
        name="WCFServiceImplementation.StudentService">
        <endpoint address="" binding="basicHttpBinding" 
		contract="WCFServiceImplementation.IStudentService">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
      
    </services>
    
    <behaviors>
      <serviceBehaviors>
        <behavior name="WCFServiceHost.ServiceBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
</system.serviceModel>

如果现在我们在解决方案资源管理器中右键单击“StudentService.svc”文件并选择“在浏览器中查看”,会打开一个网页浏览器窗口,显示 WCF 服务正在运行。WCF 服务的终结点地址显示在浏览器地址栏中:

ViewInBrowser.jpg

示例 WCF 客户端应用程序“WCFCallExample”

以下是在解决方案资源管理器中显示的简单 WPF 应用程序“WCFCallExample”。此应用程序是用于访问之前构建的 WCF 服务的示例客户端应用程序。

VisualStudioWPFApplicationFull.jpg

使用 Visual Studio 提供的“添加服务引用”实用工具,将“服务引用” “StudentService”添加到项目中。添加“StudentService”引用后,Visual Studio 将生成所有用于访问 WCF 服务的客户端代理。添加此引用时,您需要单击“高级...”按钮并打开“服务引用设置”窗口。您需要确保“生成异步操作”复选框已选中。

ProxyGenerateAsync.jpg

通过勾选此复选框,Visual Studio 将生成支持对 WCF 服务进行同步和异步调用的代理。

在“WCFCallExample.xaml”及其代码隐藏文件中实现的“WPF 用户控件” “WCFCallExample”将用于演示使用生成的代理对 WCF 服务进行同步和异步调用。“WCFCallExample.xaml”文件如下所示:

<UserControl x:Class="WFCCallExample.WCFCallExample"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:Toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <Grid VerticalAlignment="Stretch"
          HorizontalAlignment="Stretch" Margin="10,10,10,10">
        
        <Grid.RowDefinitions>
            <RowDefinition Height="23" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        
        <StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right">
            <TextBlock Style="{StaticResource SmallBold}">
                Select the number of the students:
            </TextBlock>
            
            <ComboBox Margin="10,0,0,0" Name="cmbNumberOfStudents" Width="80"
                      SelectionChanged="cmbNumberOfStudents_SelectionChanged" />
            
            <Button Margin="10,0,0,0" Width="150" Click="WCFServiceCall_Click">
                Make WCF service call</Button>
            
            <Button Margin="10,0,0,0" Width="150" Click="ClickMe_Click">
                Click me!</Button>
        </StackPanel>
        
        <Toolkit:DataGrid Margin="0,10,0,10" AutoGenerateColumns="False"
                          Grid.Row="1" Name="DGStudent" IsReadOnly="True">
            <Toolkit:DataGrid.Columns>
                <Toolkit:DataGridTextColumn Header="StudentID" 
			Binding="{Binding StudentID}" />
                <Toolkit:DataGridTextColumn Header="StudentName" 
			Binding="{Binding StudentName}" />
                <Toolkit:DataGridTextColumn Header="Score" Binding="{Binding Score}" />
                <Toolkit:DataGridTextColumn Header="EvaluationTime" 
			Binding="{Binding EvaluationTime}" />
            </Toolkit:DataGrid.Columns>
        </Toolkit:DataGrid>
    </Grid>
</UserControl>

此“XAML”文件定义了以下主要功能性“UI 元素”:

  • 一个“ComboBox”,用于选择从 WCF 服务检索的学生数量。
  • 一个“DataGrid”,用于显示从 WCF 服务获取的学生信息。
  • 一个标记为“Make WCF service call”的“Button”,用于发起 WCF 调用。
  • 一个标记为“Click me!”的“Button”,用于弹出一个简单的“MessageBox”。此按钮用于显示 UI 线程是否被 WCF 调用阻塞。

WCFCallExample.xaml”的代码隐藏文件如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ServiceModel;
 
namespace WFCCallExample
{
    /// <summary>
    /// Interaction logic for WCFCallExample.xaml
    /// </summary>
    public partial class WCFCallExample : UserControl
    {
        private bool AsynchronousCall = false;
 
        public WCFCallExample(bool AsynchronousCall)
        {
            InitializeComponent();
            this.AsynchronousCall = AsynchronousCall;
 
            cmbNumberOfStudents.Items.Add("****");
            cmbNumberOfStudents.SelectedIndex = 0;
            for (int Idex = 5; Idex <= 30; Idex = Idex + 5)
            {
                cmbNumberOfStudents.Items.Add(Idex);
            }
        }
 
        private void ClickMe_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("OK, I am clicked ...");
        }
 
        private void WCFServiceCall_Click(object sender, RoutedEventArgs e)
        {
            if (cmbNumberOfStudents.SelectedValue.ToString() == "****")
            {
                MessageBox.Show("Please select the number of the students to retrieve");
                return;
            }        
 
            DGStudent.ItemsSource = null;
 
            int NumberOfStudents =
                System.Convert.ToInt16(cmbNumberOfStudents.SelectedValue);
 
            if (AsynchronousCall) { MakeAsynchronousCall(NumberOfStudents); }
            else { MakeSynchronousCall(NumberOfStudents); }
        }
 
        private void cmbNumberOfStudents_SelectionChanged(object sender,
            SelectionChangedEventArgs e)
        {
            DGStudent.ItemsSource = null;
        }
 
        private void MakeSynchronousCall(int NumberOfStudents)
        {
            StudentService.StudentServiceClient WCFClient =
                new WFCCallExample.StudentService.StudentServiceClient();
            
            try
            {
                WCFClient.Open();
                List<StudentService.Student>  Students =
                    WCFClient.GetStudents(NumberOfStudents);
 
                DGStudent.ItemsSource = Students;
            }
            catch (Exception ex){ MessageBox.Show(ex.Message); }
            finally
            {
                if (WCFClient.State ==
                    System.ServiceModel.CommunicationState.Opened)
                {
                    WCFClient.Close();
                }
            }
        }
 
        private void MakeAsynchronousCall(int NumberOfStudents)
        {
            BasicHttpBinding basicHttpBinding = new BasicHttpBinding();
            StudentService.StudentServiceClient c =
                new WFCCallExample.StudentService.StudentServiceClient();
            EndpointAddress endpointAddress = c.Endpoint.Address;
 
            StudentService.IStudentService iStudentService =
                new ChannelFactory<StudentService.IStudentService>
                (basicHttpBinding, endpointAddress).CreateChannel();
 
            AsyncCallback aSyncCallBack =
                delegate(IAsyncResult result)
            {
                try
                {
                    List<StudentService.Student> Students =
                        iStudentService.EndGetStudents(result);
 
                    this.Dispatcher.BeginInvoke((Action)delegate
                    { DGStudent.ItemsSource = Students; });
                }
                catch (Exception ex)
                {
                    this.Dispatcher.BeginInvoke((Action)delegate
                    { MessageBox.Show(ex.Message); });
                }
            };
 
            try
            {
                iStudentService.BeginGetStudents(NumberOfStudents,
                    aSyncCallBack, iStudentService);
            } catch (Exception ex) { MessageBox.Show(ex.Message); }
        }
    }
}

此代码隐藏文件执行以下操作:

  • 它定义了一个 private 布尔类型实例变量“AsynchronousCall”,该变量将在类的构造函数中初始化。当点击“Make WCF service call”按钮时,该布尔变量将由事件处理方法“WCFServiceCall_Click”用于决定是对 WCF 服务进行同步调用还是异步调用。
  • 如果需要进行同步调用,则使用“MakeSynchronousCall”方法。它将发起一个同步 WCF 调用,并将 WCF 服务返回的学生信息列表绑定到“DataGrid”。
  • 如果应该进行异步调用,则使用“MakeAsynchronousCall”方法。它将发起一个异步调用,并在“AsyncCallback” “委托”中,将 WCF 服务返回的学生信息列表绑定到“DataGrid”。

通过使用 Visual Studio 生成的代理,对 WCF 服务的同步和异步调用都非常简单。进行同步调用时,发起调用的 UI 线程将被阻塞,直到 WCF 服务将结果发送回客户端。进行异步调用时,UI 线程发送 WCF 请求并派生一个工作线程来等待 WCF 服务的响应。UI 线程不会被阻塞,并保持对其他用户交互的响应。当 WCF 服务的响应返回时,“AsyncCallback” “委托”被执行以处理 WCF 服务响应。

此“用户控件” “WCFCallExample”托管在应用程序主窗口“MainWnd.xaml”中:

 <Window x:Class="WFCCallExample.MainWnd"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Style="{StaticResource WindowDefault}"
    Title="{Binding Path=ApplicationName, Mode=OneTime}"
        WindowStartupLocation="CenterScreen"
        WindowState="Maximized"
        Icon="Images/Rubik-Cube.ico">
    
    <Grid VerticalAlignment="Stretch"
          HorizontalAlignment="Stretch" Margin="5,20,5,5">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="20" />
        </Grid.RowDefinitions>
        
        <Grid Grid.Row="0">
            <Grid.RowDefinitions>
                <RowDefinition Height="60" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            
            <Grid Grid.Row="0" HorizontalAlignment="Stretch">
                <Grid.RowDefinitions>
                    <RowDefinition Height="20" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                
                <TextBlock Grid.Row="0" Style="{StaticResource AppHeader}"
                           HorizontalAlignment="Center"
                           Text="{Binding Path=ApplicationName, Mode=OneTime}" />
                
                <StackPanel Grid.Row="1" Orientation="Horizontal" 
				HorizontalAlignment="Center">
                    <TextBlock xml:space="preserve"
                               Style="{StaticResource AuthorInformation}">
						Developed by </TextBlock>
                    <TextBlock xml:space="preserve" 
			Style="{StaticResource AuthorInformation}"
                               Text="{Binding Path=Author, Mode=OneTime}"></TextBlock>
                    <TextBlock xml:space="preserve"
                               Style="{StaticResource AuthorInformation}"> on </TextBlock>
                    <TextBlock xml:space="preserve" 
			Style="{StaticResource AuthorInformation}"
                               Text="{Binding Path=DevelopentDate, Mode=OneTime}">
		  </TextBlock>
                    <TextBlock xml:space="preserve"
                               Style="{StaticResource AuthorInformation}"> Version 
		  </TextBlock>
                    <TextBlock xml:space="preserve" 
			Style="{StaticResource AuthorInformation}"
                               Text="{Binding Path=Version, Mode=OneTime}"></TextBlock>
                </StackPanel>
            </Grid>
             
            <Grid Grid.Row="1" x:Name="MainContent" HorizontalAlignment="Stretch" 
              VerticalAlignment="Stretch" Margin="5, 2, 5, 10">
                <TabControl FontSize="12" x:Name="ApplicationMainTab">
                    <TabItem
                    HeaderTemplate="{StaticResource TabItemHeaderTemplate}">
                        <TabItem.Header>
                            Call WCF Services Synchronously
                        </TabItem.Header>
                    </TabItem>
  
                    <TabItem
                    HeaderTemplate="{StaticResource TabItemHeaderTemplate}">
                        <TabItem.Header>
                            Call WCF Services Asynchronously
                        </TabItem.Header>
                    </TabItem>
                </TabControl>
  
            </Grid>
        </Grid>
  
        <TextBlock Grid.Row="1" Text="{Binding Path=Copyright, Mode=OneTime}"
                   HorizontalAlignment="Center" Style="{StaticResource SmallBold}"
                   Foreground="Silver" />
    </Grid>
</Window>

上述“XAML”文件在一个“TabControl”中定义了两个“选项卡项”。每个“选项卡项”都将托管在“WCFCallExample.xaml”文件中创建的“用户控件”的一个实例。通过相应地设置布尔实例变量“AsynchronousCall”,一个实例将被初始化为进行同步 WCF 调用,另一个实例将被初始化为进行异步 WCF 调用。

MainWnd.xaml”的代码隐藏文件如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using WFCCallExample.Properties;
 
namespace WFCCallExample
{
    /// <summary>
    /// Interaction logic for MainWnd.xaml
    /// </summary>
    public partial class MainWnd : Window
    {
        public MainWnd()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(MainWnd_Loaded);
        }
 
        void MainWnd_Loaded(object sender, RoutedEventArgs e)
        {
            DataContext = Settings.Default;
 
            ((TabItem)ApplicationMainTab.Items[0]).Content
                = new WCFCallExample(false);
 
            ((TabItem)ApplicationMainTab.Items[1]).Content
                = new WCFCallExample(true);
        }
    }
}

MainWnd_Loaded”事件处理程序初始化两个“WCFCallExample” “用户控件”对象实例,并将它们分别关联到相应的“选项卡项”。其中一个“用户控件”对象实例被初始化为进行同步调用,另一个被初始化为进行异步调用。

运行应用程序

现在,我们完成了这个示例应用程序的开发。将“WCFCallExample”项目设置为“启动项目”并按“F5”,您可以调试运行该应用程序。让我们首先看看“Call WCF Services Synchronously”选项卡:

WCFCallSynchronized.jpg

选择要从 WCF 服务检索的学生数量,然后点击“Make WCF service call”按钮,会发起一个同步 WCF 调用,并从 WCF 服务检索学生列表。在等待 WCF 服务响应您的调用期间,您可以点击“Click me!”按钮,您会发现 WPF 应用程序的 UI 线程被阻塞,应用程序在 WCF 调用完成之前不会响应您的按钮点击。

现在让我们看看“Call WCF Services Asynchronously”选项卡:

WCFCallAsynchronized.jpg

选择要检索的学生数量并点击“Make WCF service call”按钮后,会发起一个异步 WCF 调用。如果您点击“Click me!”按钮,您会发现 UI 线程没有被阻塞,并且显示按钮被点击的“MessageBox”会立即出现,甚至在 WCF 服务响应 WCF 调用之前。

关注点

  • 本文通过一个示例向您展示了如何构建 WCF 服务以及如何同步和异步调用 WCF 服务。
  • 在构建 WCF 服务时,我将服务的实现和宿主分开了。这并非总是必要的。本文只是向您展示,如果您愿意,可以分离实现和宿主的关注点。
  • 要同步调用 WCF 服务,您可以简单地使用 Visual Studio 创建的代理方法,类似于调用本地方法。
  • 要异步调用 WCF 服务,您需要首先创建一个“AsyncCallback” “委托”,并将此委托传递给 Visual Studio 生成的异步代理方法。由于“AsyncCallback” “委托”是在与 UI 线程不同的工作线程中执行的,因此任何对“UI 元素”的访问都需要进行“调度”。
  • 从应用程序的角度来看,两种方式各有利弊。对于大多数应用程序,在调用 WCF 服务后,应用程序将处于中间状态,直到收到服务的响应。在此期间,阻塞 UI 线程以防止用户操作导致不可预测的结果可能是理想的。在这种情况下,同步调用可能适用。在其他一些场景中,特别是当我们处理某些“SOA”应用程序时,我们可能需要同时进行多个服务调用,并让服务并行工作。在这种情况下,异步 WCF 调用将是理想的选择。
  • 我突然想到一件事。为了进行异步调用,您可能不需要异步代理。您可以自行派生一个工作线程,并让该线程执行同步调用。您将获得与使用异步代理相同的效果。
  • 如果您不熟悉 WCF 服务,此链接是一个很好的入门资源。

历史

这是本文的第一个修订版。

© . All rights reserved.