【发布时间】:2014-03-25 15:16:54
【问题描述】:
我目前正在将客户端应用程序迁移到 .NET 4.5 以使用 async/await。该应用程序是当前仅提供同步服务的 WCF 服务的客户端。我现在想知道,我应该如何异步消费这个同步服务?
我正在使用channel factories 连接到 WCF 服务,利用服务器和客户端之间共享的服务合同。因此,我无法使用 VisualStudio 或 svcutil 的自动生成功能来生成异步客户端代理。
我读过this related question,它是关于是否使用Task.Run 在客户端包装同步调用,或者是否使用异步方法扩展服务合同。答案表明,由服务器提供“真正的”异步方法对客户端性能更好,因为没有线程必须主动等待服务调用完成。这对我来说确实很有意义,这意味着同步调用应该包装在服务器端。
另一方面,Stephen Toub 在this blog post 中普遍反对这样做。现在,他在那里没有提到 WCF,所以我不确定这是否仅适用于在同一台机器上运行的库,或者它是否也适用于远程运行的东西,但异步性的引入对连接/传输。
毕竟,由于服务器实际上并没有异步工作(而且很可能不会再有一段时间),一些线程将总是需要等待:无论是在客户端还是在服务器上。这也适用于同步使用服务时(当前,客户端等待后台线程以保持 UI 响应)。
示例
为了让问题更清楚,我准备了一个例子。完整的项目可用于download here。
服务器提供同步服务GetTest。这是目前存在的,也是工作发生的地方——同步。一种选择是将其包装在异步方法中,例如使用Task.Run,并将该方法作为合同中的附加服务提供(需要扩展合同接口)。
// 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";
}
现在,在客户端,此服务是使用通道工厂创建的。这意味着我只能访问服务契约定义的方法,尤其是我无法访问异步服务方法,除非我明确定义和实现它们。
根据现在可用的方法,我有两种选择:
-
我可以通过包装调用来异步调用同步服务:
await Task.Run<string>(() => svc.GetTest()); -
我可以直接异步调用服务器提供的异步服务:
await svc.GetTestAsync();
两者都可以正常工作,并且不会阻止客户端。这两种方法都涉及到某个端的忙等待:选项 1 在客户端等待,这相当于之前在后台线程中所做的。选项 2 通过将同步方法包装在那里等待服务器。
使同步 WCF 服务异步感知的推荐方法是什么?我应该在哪里执行包装,在客户端还是在服务器上?或者有没有更好的选择来做到这一点而无需在任何地方等待,即通过在连接上引入“真正的”异步——就像生成的代理一样?
【问题讨论】:
-
@Servy 客户端在不同的机器上,通过网络使用服务。当然,让客户端完全异步是有意义的,更不用说没有阻塞的 UI。
-
如果您围绕 API 的同步方法调用
Task.Run,您可能做错了什么。当服务器同步进行处理时,从客户端到服务器的网络请求是异步的,这很好。这样做时,异步围绕网络请求;并且该网络请求不关心服务器方法是异步还是同步。 -
@StephenCleary 我没有使用代理,我使用客户端工厂,所以我不会自动拥有该选项。我必须自己添加异步功能。
-
@Servy 这正是我问这个问题的原因。服务器同步地完成它的工作,并且——作为它的客户端——我希望能够异步地使用这些服务。但我不知道如何正确地做到这一点。
-
@Servy 我没有使用代理,所以我无法自动生成这样的异步 API……
标签: c# .net wcf async-await