我目前正在将客户端应用程序迁移到.NET 4.5,以利用async/await。该应用程序是一个客户端,用于连接WCF服务,该服务当前仅提供同步服务。现在我想知道,如何异步使用这个同步服务?
我正在使用通道工厂连接WCF服务,利用了在服务器和客户端之间共享的服务契约。因此,我无法使用Visual Studio或svcutil
自动生成异步客户端代理。
我已经阅读了相关问题,其中讨论了在客户端上是否应使用Task.Run
包装同步调用,或者是否应扩展服务契约以支持异步方法。回答建议为客户端性能提供“真正”的异步方法更好,因为没有线程需要主动等待服务调用完成。这对我来说非常合理,并且意味着同步调用应该在服务器端进行包装。
另一方面,Stephen Toub在这篇博客文章中一般不建议这样做。现在,他没有在那里提到WCF,因此我不确定这是否仅适用于在同一台机器上运行的库,还是也适用于远程运行但引入异步对连接/传输产生实际影响的情况。
而且毕竟,由于服务器实际上并未以异步方式工作(并且可能不会在另一个时间内),某些线程始终需要等待:无论是在客户端还是在服务器上。这也适用于同步使用服务时(目前,客户端在后台线程上等待以保持UI响应)。
示例
为了更清楚地说明问题,我准备了一个示例。完整的项目可以在这里下载。
服务器提供同步服务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服务支持异步?我应该在客户端还是服务器上执行包装?或者有更好的选项可以在不必等待任何地方的情况下实现“真正”的异步性连接,例如生成的代理所做的那样?
Task.Run
,那么您可能做错了什么。当客户端向服务器发出网络请求时,异步请求是可以接受的,而服务器同步处理该请求也是可以接受的。在这种情况下,异步是针对网络请求的;而该网络请求并不关心服务器方法是异步还是同步的。 - Servy