如何同步和异步调用 WCF 服务
本文将通过一个示例向您展示如何同步和异步调用 WCF 服务。
引言
本文将通过一个示例向您展示如何同步和异步调用 WCF 服务。
背景
Windows Communication Foundation (WCF) 是 .NET Framework 中的一个应用程序编程接口,用于构建互联的面向服务的应用程序。 WCF 客户端有两种方式访问 WCF 服务提供的功能。它们是同步和异步 WCF 调用。
本文将通过一个示例向您展示如何以同步和异步两种方式访问 WCF 服务。本文假设读者对 WCF 服务有基本的了解。如果您不熟悉 WCF 服务,此链接是一个很好的入门资源。在本文中,我将首先构建一个 WCF 服务。然后我将向您展示如何访问此服务。以下是此示例 Visual Studio 解决方案在解决方案资源管理器中的屏幕截图:

此 Visual Studio 解决方案包含三个 .NET 项目:
- “
WCFServiceImplementation
”项目是一个实现 WCF 服务的类库。 - “
WCFServiceHost
”项目是一个 ASP.NET Web 应用程序,它托管在“WCFServiceImplementation
”项目中实现的 WCF 服务。 - “
WCFCallExample
”项目是一个 WPF 应用程序,我将在此演示访问 WCF 服务的两种方式。
此 Visual Studio 解决方案是在 Visual Studio 2008 中使用 C# 开发的。我们将首先了解 WCF 服务是如何实现的。
WCF 服务的实现

.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
”中,在解决方案资源管理器中显示如下:

在该项目中,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 服务的终结点地址显示在浏览器地址栏中:

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

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

通过勾选此复选框,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>
- 一个“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”选项卡:

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

选择要检索的学生数量并点击“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 服务,此链接是一个很好的入门资源。
历史
这是本文的第一个修订版。