我目前正在将客户端应用程序迁移到 .NET 4.5 以使用 async/await。该应用程序是当前仅提供同步服务的 WCF 服务的客户端。我现在想知道,我应该如何异步使用这个同步服务?
我正在使用 channel factories使用在服务器和客户端之间共享的服务契约(Contract)连接到 WCF 服务。因此,我无法使用 VisualStudio 或 svcutil
的自动生成来生成异步客户端代理。
我已阅读 this related question这是关于是否使用 Task.Run
在客户端包装同步调用,或者是否改为使用异步方法扩展服务契约。答案表明,服务器提供“真正的”异步方法对客户端性能更好,因为没有线程必须主动等待服务调用完成。这对我来说确实很有意义,这意味着同步调用应该包装在服务器端。
另一方面,Stephen Toub 在 this blog post 中一般不鼓励这样做.现在,他没有在那里提到 WCF,所以我不确定这是否只适用于在同一台机器上运行的库,或者它是否也适用于远程运行的东西,但异步性的引入对连接/传输。
毕竟,由于服务器实际上并不是异步工作的(并且很可能暂时不会),一些线程将始终需要等待:无论是在客户端还是在服务器上。这在同步使用服务时也适用(目前,客户端在后台线程上等待以保持 UI 响应)。
例子
为了更清楚地说明问题,我准备了一个例子。完整项目可用于 download here .
服务器提供同步服务GetTest
。这是当前存在的,也是同步进行工作的地方。一种选择是将其包装在异步方法中,例如使用 Task.Run
,并将该方法作为契约(Contract)中的附加服务提供(需要扩展契约(Contract)接口(interface))。
// currently available, synchronous service
public string GetTest() {
Thread.Sleep(2000);
return "foo";
}
// possible asynchronous wrapper around existing service
public Task<string> GetTestAsync() {
return Task.Run<string>(() => this.GetTest());
}
// ideal asynchronous service; not applicable as work is done synchronously
public async Task<string> GetTestRealAsync() {
await Task.Delay(2000);
return "foo";
}
现在,在客户端,该服务是使用 channel 工厂创建的。这意味着我只能访问服务契约定义的方法,尤其不能访问异步服务方法,除非我明确定义和实现它们。
根据现在可用的方法,我有两个选择:
我可以通过封装调用来异步调用同步服务:
await Task.Run<string>(() => svc.GetTest());
我可以直接异步调用服务端提供的asynchronous服务:
await svc.GetTestAsync();
两者都工作正常,并且不会阻塞客户端。这两种方法都涉及在某个端忙等待:选项 1 在客户端等待,这相当于之前在后台线程中完成的操作。选项 2 通过在服务器上包装同步方法在服务器上等待。
使同步 WCF 服务异步感知的推荐方法是什么?我应该在哪里执行包装,在客户端还是在服务器上?或者是否有更好的选择来做到这一点而无需在任何地方等待,即通过在连接上引入“真正的”异步性——就像生成的代理一样?
最佳答案
从异步的角度来看,客户端和服务器端是完全分开的,它们根本不关心彼此。你应该在你的服务器上有你的同步功能,并且只有你的服务器上有同步功能。
如果你想“正确”地做到这一点,在客户端你将无法重用与用于生成服务器的接口(interface)相同的接口(interface)来生成你的 channel 工厂。
所以你的服务器端看起来像这样
using System.ServiceModel;
using System.Threading;
namespace WcfService
{
[ServiceContract]
public interface IService
{
[OperationContract]
string GetTest();
}
public class Service1 : IService
{
public string GetTest()
{
Thread.Sleep(2000);
return "foo";
}
}
}
你的客户端看起来像这样
using System;
using System.Diagnostics;
using System.ServiceModel;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SandboxForm
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var button = new Button();
this.Controls.Add(button);
button.Click += button_Click;
}
private async void button_Click(object sender, EventArgs e)
{
var factory = new ChannelFactory<IService>("SandboxForm.IService"); //Configured in app.config
IService proxy = factory.CreateChannel();
string result = await proxy.GetTestAsync();
MessageBox.Show(result);
}
}
[ServiceContract]
public interface IService
{
[OperationContract(Action = "http://tempuri.org/IService/GetTest", ReplyAction = "http://tempuri.org/IService/GetTestResponse")]
Task<string> GetTestAsync();
}
}
关于c# - 异步使用同步 WCF 服务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22638828/