WCF 中的消息交换模式和异步操作教程






4.96/5 (15投票s)
本文讨论了 WCF 服务支持的各种消息交换模式以及如何异步调用 WCF 操作
引言
本文将讨论 WCF 服务支持的各种消息交换模式。我们还将讨论如何异步调用 WCF 服务函数。我们将详细探讨每种消息交换技术,并尝试为每种技术提供一个示例应用程序。
背景
从应用程序代码调用函数总是遵循简单的请求-响应模式,即我们调用函数,在函数执行期间调用者将等待函数(阻塞),一旦函数调用返回,调用代码将再次响应。
现在,这种简单的请求-响应模式对于执行时间很短的函数来说是没问题的。但是,如果被调用的函数需要相当长的时间才能执行,那么调用代码将在该期间无响应。这将给需要用户交互的应用程序带来严重问题。
在简单的库中,我们可以异步调用函数,让我们的应用程序保持响应,然后单独处理函数的响应。从 WCF 服务的角度来看,这种异步函数调用模型也是允许的,但 WCF 还提供了一些开箱即用的消息交换模式来提供此类功能。
在查看消息交换模式之前,让我们思考一下服务消费者可能需要的场景。
- 场景 1:我们有一些函数调用不需要太多时间,让应用程序等待这些函数是完全可以接受的。
- 场景 2:我们有一些耗时的函数调用,但调用该函数更像是我们应用程序代码中的一个触发器。我们只想调用函数,不想等待函数的响应。
- 场景 3:我们有一个长时间运行的函数,我们希望以非阻塞方式调用该函数,同时我们希望从函数(响应)中获取一些内容到我们的应用程序中。
- 场景 4:我们希望有一个双向通信机制(如事件),我们将客户端连接到服务,调用一个函数并返回。服务反过来可以调用一个回调函数,让应用程序知道响应。
现在 WCF 提供了以某种方式配置我们的服务和服务代理的可能性,使得所有这些场景都可以实现。从消息交换模式的角度来看,WCF 支持第 1、2 和 4 种场景。要使第 3 种场景工作,我们需要在客户端代理上实现异步函数调用。
消息交换模式
WCF 提供 3 种消息交换模式
-
请求/响应
-
单向
-
双工
请求/响应模式是默认且最广泛使用的模式。在此模式下,应用程序等待/阻塞直到服务函数调用完成(场景 1)。默认的 OperationContract
行为仅为请求/响应,但如果我们需要显式指定它,则必须将 OperationContract
的 IsOneWay
属性配置为 false
。
单向指定函数调用不会阻塞。客户端将简单地调用函数,控制权将返回给调用者(场景 2)。要使函数符合单向模式,我们必须将 OperationContract
的 IsOneWay
属性设置为 true
。
双工为我们提供了将回调函数/对象连接到服务的可能性(场景 4)。这将需要一个客户端需要实现的额外接口。如果与此回调接口一起,我们将函数调用标记为 IsOneWay=true
,则函数调用将是非阻塞的,WCF 服务将在操作完成后向客户端发送回调。当客户端希望从服务接收一些状态更改信息时,即使它没有调用服务上的任何操作(更像发布者订阅者模型),此模式也很重要。
现在,从某种意义上说,场景 3 的问题也可以使用双工服务来解决。但是,双工服务有一些限制,并且它并不完全是异步调用操作。要解决场景 3 的问题,我们可以异步调用长时间运行的函数(我们很快就会看到)。
使用代码
现在让我们创建一个简单的 WCF 服务,它将暴露 3 个函数。一个函数以请求/响应模式调用,即 IsOneWay=false
。第二个函数使用单向模式调用,即设置 IsOneWay=true
。最后,第三个函数将调用客户端的回调以通知调用已完成。
现在,由于我们还需要演示双工服务行为,让我们只创建一个双工服务。前两个函数在非双工服务中也会以相同的方式工作。只有第三个函数将利用双工服务的优势。现在创建双工服务的步骤是
- 创建服务契约:我们将创建一个简单的服务契约
ISampleService
。 - 创建回调契约:我们将创建一个名为
ICallBack
的简单接口作为回调契约。 - 将回调契约与服务契约关联:我们通过在服务契约中指定属性
CallbackContract=typeof(ICallBack)
来实现。 - 公开服务:使用 wsDualHttpBinding。
- 实现回调契约:我们将创建一个名为
CallbackHandler
的简单类并实现回调契约接口(代码如下)。 - 创建
InstanceContext
并传递回调契约实例:这也在客户端完成(代码如下)。 - 在代理类的构造函数中传递
InstanceContext
对象:这也在客户端代码中完成(代码如下)。
[ServiceContract(CallbackContract=typeof(ICallBack))]
public interface ISampleService
{
// Request-Response Mode
[OperationContract]
void TestFunction1();
// One Way mode
[OperationContract(IsOneWay=true)]
void TestFunction2();
// Duplex service mode
[OperationContract(IsOneWay = true)]
void TestFunction3();
}
[ServiceContract] // Callback interface to be implemented by client
public interface ICallBack
{
[OperationContract(IsOneWay=true)]
void NotifyClient(string message);
}
上面的代码片段展示了服务契约和操作契约。现在这个服务是一个双工服务,为此我们配置了服务契约的 CallBackContract
属性以指向我们定义的回调接口。
注意:TestFunction1
和 TestFunction2
将不会利用双工服务行为。即使服务不是双工服务,它们也将以相同的方式工作。所以我们可以毫无问题地测试其他两种模式。
现在这些函数的实现将模拟一些长时间运行的过程。我们通过休眠 5 秒钟来做到这一点。
public class SampleService : ISampleService
{
public void TestFunction1()
{
// Let us simulate some time consuming function
Thread.Sleep(5000);
}
public void TestFunction2()
{
// Let us simulate some time consuming function
Thread.Sleep(5000);
}
public void TestFunction3()
{
// Let us simulate some time consuming function
Thread.Sleep(5000);
ICallBack callBack = OperationContext.Current.GetCallbackChannel<ICallBack>();
callBack.NotifyClient("TestFunction3 has been successfully executed");
}
}
此服务现在将使用 wsDualHttpBinding
绑定公开,因为此绑定将支持双工通信模式。
测试服务客户端
让我们创建一个简单的 Windows 应用程序,以便我们可以查看在进行函数调用时 GUI 是否响应。这将是一个简单的应用程序,它将有独立的按钮来调用每个函数。函数的响应将显示在 UI 上。

我们将在此客户端应用程序中添加服务引用,并逐个测试每个函数调用。
请求/响应
TestFunction1
配置为请求-响应模式,即 IsOneWay
设置为 false(默认)。使用客户端代理调用此函数的代码是
InstanceContext instance = new InstanceContext(new CallbackHandler());
ServiceReference1.SampleServiceClient client = new ServiceReference1.SampleServiceClient(instance);
listBox1.Items.Add("Request/Response: Calling the WCF service function.");
client.TestFunction1();
listBox1.Items.Add("Request/Response: WCF service function call complete.");
client.Close();
从客户端调用此函数将使 UI 挂起 5 秒钟,因为调用代码正在等待函数调用完成。

单向
TestFunction2
配置为单向模式,即 IsOneWay
设置为 true。使用客户端代理调用此函数的代码是
InstanceContext instance = new InstanceContext(new CallbackHandler());
ServiceReference1.SampleServiceClient client = new ServiceReference1.SampleServiceClient(instance);
listBox1.Items.Add("One way: Calling the WCF service function.");
client.TestFunction2();
listBox1.Items.Add("One way: WCF service function call complete.");
client.Close();
从客户端调用此函数不会使 UI 无响应,因为调用代码不等待函数调用完成。它只是调用函数并返回,假设函数会完成它的工作。

注意:需要 InstanceContext
模式是因为我们创建了一个双工服务。如果我们没有创建双工服务,那么它是不需要的,并且将调用 SampleServiceClient
的无参数构造函数。在非双工服务的情况下,只需要此更改,代码将正常工作。
双工
现在,为了能够从服务接收回调,我们首先需要实现服务的回调接口。
// Internal class that implements the callback interface
class CallbackHandler : ServiceReference1.ISampleServiceCallback
{
public void NotifyClient(string message)
{
MessageBox.Show(message);
}
}
此回调类的实例将传递到 InstanceContext
中,并且此 InstanceContext
将传递给服务,以便服务可以与客户端建立连接并调用回调函数。调用函数是相同的,但我们需要使 SampleServiceClient
成为类级别的成员变量,以便当服务发送回调时实例仍然存在。
InstanceContext instance = null;
ServiceReference1.SampleServiceClient client = null;
public Form1()
{
InitializeComponent();
instance = new InstanceContext(new CallbackHandler());
client = new ServiceReference1.SampleServiceClient(instance);
}
private void button3_Click(object sender, EventArgs e)
{
listBox1.Items.Add("Duplex: Calling the WCF service function.");
client.TestFunction3();
listBox1.Items.Add("Duplex: WCF service function call complete.");
}
现在调用此函数将不会阻塞 UI,因为此函数也标记为 IsOneWay=true
。但是,由于函数实现调用客户端的回调函数,因此一旦函数调用完成(5 秒后),函数实现 NotifyClient
将被调用,并将向我们显示消息框。

异步调用操作
现在,如果我们有一个长时间运行的进程,它既没有标记为单向操作,也不可能拥有双工服务,那么从客户端的角度来看,唯一的选择是异步调用此方法。异步调用方法使我们能够拥有响应式调用代码,同时也能在客户端捕获到正确的响应消息。
实现异步方法调用的第一件事是配置代理以生成对异步函数的支持。

现在这为我们提供了所有函数的异步版本以及在函数调用完成时将调用的回调事件。所以让我们再次调用 Function1
,但这次是异步的
InstanceContext instance = new InstanceContext(new CallbackHandler());
ServiceReference1.SampleServiceClient client = new ServiceReference1.SampleServiceClient(instance);
listBox1.Items.Add("Request/Response: Calling the WCF service function async");
client.TestFunction1Completed += new EventHandler<AsyncCompletedEventArgs>(client_TestFunction1Completed);
client.TestFunction1Async();
listBox1.Items.Add("Request/Response: WCF service function async call complete.");
client.Close();
完成后,将调用事件处理程序并显示消息框,指示函数调用已完成。

注意:这是异步模式的客户端实现,其中客户端应用程序在单独的线程中调用操作并管理与主线程的通信。我们还可以在 WCF 服务上创建异步操作,其中在服务内部创建单独的线程。这是通过将 OperationContract
的 AsyncPattern
属性设置为 true
来完成的。但这本身就是一个相当大的主题,可能需要单独讨论。在本文中讨论它会使本文有点离题。
一些重要的点
请求-响应模式是最常见和广泛使用的模式。如果我们使用其他模式,那么它们会带来一些限制。在决定在我们的 WCF 服务中使用哪种模式之前,让我们看看这些限制。
单向:当我们使用单向模式时,一旦函数被调用,调用者和被调用者之间就没有联系。这没问题,因为我们需要这种行为。但这里的症结在于,调用者无法知道函数调用是成功还是失败。我们无法将任何错误传播回客户端,因为当函数执行正在进行时,客户端和服务之间的链接将不再存在。
双工:双工服务在某些受限绑定上工作。并非所有绑定都支持双工通信。双工服务需要在客户端和服务器之间建立连接。由于网络设计限制,这并非总是可能的。双工服务还可能引发一些微妙的线程关系问题,这些问题可能难以修复或可能导致一些意外行为。
关注点
在本文中,我们看到了 WCF 服务支持的所有三种可能的消息交换模式,以及如何从客户端异步调用操作。本文中显示的代码片段仅显示了上下文中所需的最少代码。建议查看示例应用程序以获得全面的理解。希望本文对您有所帮助。
历史
- 2013 年 3 月 30 日:第一版。