简体   繁体   English

异步使用同步WCF服务

[英]Asynchronously consume synchronous WCF service

I'm currently in the process of migrating a client application over to .NET 4.5 to make use of async/await. 我目前正在将客户端应用程序迁移到.NET 4.5以使用async / await。 The application is a client for a WCF service which currently offers only synchronous services. 该应用程序是WCF服务的客户端,该服务目前仅提供同步服务。 I am wondering now, how should I asynchronously consume this synchronous service ? 我现在想知道, 我应该如何异步使用这种同步服务

I am using channel factories to connect to the WCF service, utilizing a service contract that is shared between both server and client. 我使用渠道工厂连接到WCF服务,利用服务器和客户端共享的服务合同。 As such, I cannot use the auto-generation from VisualStudio or svcutil to generate asynchronous client proxies. 因此,我无法使用VisualStudio或svcutil的自动生成来生成异步客户端代理。

I have read this related question which is about whether to wrap the synchronous call on the client-side using Task.Run , or whether to extend the service contract with async methods instead. 我已经阅读了这个相关的问题该问题是关于是否使用Task.Run在客户端包装同步调用,或者是否使用异步方法扩展服务契约。 The answer suggests that having “real” asynchronous methods offered by the server is better for the client performance as no thread will have to actively wait for the service call to finish. 答案表明,服务器提供的“真正”异步方法对于客户端性能更好,因为没有线程必须主动等待服务调用完成。 This does make a lot of sense to me, and it would mean that the synchronous calls should be wrapped on the server-side. 这对我来说很有意义,这意味着同步调用应该包含在服务器端。

On the other hand, Stephen Toub discorages doing this in general in this blog post . 另一方面,Stephen Toub在这篇博客文章中一般都不赞成这样做。 Now, he does not mention WCF there, so I am not sure if this just applies to libraries that run on the same machine, or if it also applies to things that run remotely, but where the introduction of asynchronicity has an actual impact on the connection/transfer. 现在,他没有在那里提到WCF,所以我不确定这是否适用于在同一台机器上运行的库,或者它是否也适用于远程运行的东西,但是异步性的引入对它的实际影响是连接/传输。

And after all, as the server does not actually work asynchronously anyway (and likely won't for another while), some threads will always have to wait: Either on the client or on the server. 毕竟,由于服务器实际上并不是异步工作(并且可能不会在另一个时间内工作),一些线程将始终必须等待:在客户端或服务器上。 And that does also apply when consuming the services synchronously (currently, the client waits on a background thread to keep the UI responsive). 这同样适用于同步使用服务(当前,客户端在后台线程上等待以保持UI响应)。

Example

To make the problem more clear, I have prepared an example. 为了使问题更清楚,我准备了一个例子。 The full project is available for download here . 完整项目可在此处下载

The server offers a synchronous service GetTest . 服务器提供同步服务GetTest This is the one that currently exists, and where the work happens—synchronously. 这是当前存在的,并且工作同步发生的地方。 One option would be to wrap this in an asynchronous method, for example using Task.Run , and offer that method as an additional service in the contract (requiring the contract interface to be expanded). 一种选择是将其包装在异步方法中,例如使用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";
}

Now, on the client-side, this service is created using a channel factory. 现在,在客户端,此服务是使用渠道工厂创建的。 That means I only have access to the methods as defined by the service contract, and I especially don't have access to asynchronous service methods unless I explicitely define and implement them. 这意味着我只能访问服务契约定义的方法,除非我明确定义和实现它们,否则我特别无法访问异步服务方法。

Depending on which methods are now available, I have two options: 根据现有的方法,我有两种选择:

  1. I can asynchronously call the synchronous service by wrapping the call: 我可以通过包装调用来异步调用同步服务:

     await Task.Run<string>(() => svc.GetTest()); 
  2. I can asynchronously call the asynchronous service directly, which is provided by the server: 我可以直接异步调用异步服务,这是由服务器提供的:

     await svc.GetTestAsync(); 

Both works fine, and will not block the client. 两者都很好,不会阻止客户端。 Both methods involve busy waiting on some end: Option 1 waits on the client, which is equivalent to what has been done before in a background thread. 这两种方法都涉及在某一端忙于等待:选项1在客户端上等待,这相当于之前在后台线程中所做的事情。 Option 2 waits on the server by wrapping the synchronous method there. 选项2通过在那里包装同步方法在服务器上等待。

What would be the recommended way to make a synchronous WCF service async-aware? 什么是使异步感知同步WCF服务的推荐方法? Where should I perform the wrapping, on the client or on the server? 我应该在客户端或服务器上执行打包的位置? Or are there better options to do this without having to wait anywhere, ie by introducing “real” asynchronicity on the connection—like the generated proxies do? 或者有没有更好的选择,无需等待任何地方,即通过在连接上引入“真正的”异步性 - 就像生成的代理一样?

The client side and server side are totally separate from an async standpoint, they do not care about each other at all. 客户端和服务器端完全独立于异步立场,他们根本不关心彼此。 You should have your sync function on your sever and only the sync function on your server. 您应该在服务器上具有同步功能,并且只有服务器上的同步功能。

If you want to do it "right", on the client you will not be able to reuse the same interface for your generating your channel factory as the interface that is used to generate the server. 如果要“正确”执行此操作,则在客户端上,您将无法重复使用相同的接口来生成通道工厂,作为用于生成服务器的接口。

So your server side would look like this 所以你的服务器端看起来像这样

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";
        }
    }
}

and your client side would look like this 而你的客户端看起来像这样

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();
    }
}

If your server-side API can be naturally async (like your Task.Delay example, rather than the Task.Run wrapper), declare it as Task -based in the contract interface. 如果您的服务器端API可以是自然异步的(就像您的Task.Delay示例,而不是Task.Run包装器),请在合同接口中将其声明为基于Task Otherwise, just leave it synchronous (but don't use Task.Run ). 否则,只需保持同步(但不要使用Task.Run )。 Do not create multiple endpoints for the sync and async versions of the same method. 不要为同一方法的同步和异步版本创建多个端点。

The generated WSDL remains the same for async and sync contract APIs, I just found out that myself: Different forms of the WCF service contract interface . 生成的WSDL对于异步和同步合同API保持不变,我只是发现了自己: WCF服务合同接口的不同形式 Your clients will keep running unchanged. 您的客户将继续保持运营。 By making your server-side WCF method asynchronous, all you do is improve the service scalability. 通过使服务器端WCF方法异步,您所做的只是提高服务可伸缩性。 Which is a great thing to do, of course, but wrapping a synchronous method with Task.Run would rather hurt the scalability than improve it. 当然,这是一件很棒的事情,但是使用Task.Run包装同步方法会损害可扩展性而不是改进它。

Now, the client of your WCF service doesn't know if the method is implemented as synchronous or asynchronous on the server, and it doesn't need to know that. 现在,您的WCF服务的客户端不知道该方法是在服务器上实现为同步还是异步,并且它不需要知道。 The client can call your method synchronously (and block the client's thread) or it can call it asynchronously (without blocking the client's thread). 客户端可以同步调用您的方法(并阻止客户端的线程),也可以异步调用它(不阻塞客户端的线程)。 In either case, it won't change the fact that the SOAP response message will be sent to the client only when the method has fully completed on the server. 在任何一种情况下, 只有当方法在服务器上完全完成时,才会改变SOAP响应消息将被发送到客户端的事实。

In your test project , you're trying to exposes different versions of the same API under different contract names: 您的测试项目中 ,您尝试在不同的合同名称下公开相同API的不同版本:

[ServiceContract]
public interface IExampleService
{
    [OperationContract(Name = "GetTest")]
    string GetTest();

    [OperationContract(Name = "GetTestAsync")]
    Task<string> GetTestAsync();

    [OperationContract(Name = "GetTestRealAsync")]
    Task<string> GetTestRealAsync();
}

This doesn't really make sense, unless you want to give your client an option to control if the method runs synchronously or asynchronously on the server . 除非您希望为客户端提供控制方法是否在服务器上同步或异步运行的选项,否则这并没有多大意义。 I cannot see why you would want this , but even if you have your reason, you'd be better off controlling this via a method argument and a single version of the API: 我不明白为什么你会想要这个 ,但即使你有理由,你最好通过方法参数和API的单一版本来控制它:

[ServiceContract]
public interface IExampleService
{
    [OperationContract]
    Task<string> GetTestAsync(bool runSynchronously);
}

Then, in the implementation your could do: 然后,在实施中你可以这样做:

Task<string> GetTestAsync(bool runSynchronously)
{
    if (runSynchronously)
        return GetTest(); // or return GetTestAsyncImpl().Result;
    else
        return await GetTestAsyncImpl();
}

@usr explains this in great details here . @usr在这里详细解释了这一点 To sum up, it is not like the WCF service calls back your client to notify about the completion of the async operation . 综上所述, 不像 WCF服务回调客户端,通知有关异步操作的完成 Rather, it simply sends back the full SOAP response using the underlying network protocol when it's done. 相反,它只是在完成后使用底层网络协议发回完整的SOAP响应。 If you need more than that, you could use WCF callbacks for any server-to-client notifications, but that would span the boundaries of a single SOAP message. 如果您需要更多,则可以对任何服务器到客户端通知使用WCF回调 ,但这将跨越单个SOAP消息的边界。

This isn't horrible: https://stackoverflow.com/a/23148549/177333 . 这并不可怕: https//stackoverflow.com/a/23148549/177333 You just wrap your return values with Task.FromResult() . 您只需使用Task.FromResult()包装返回值。 You have to change the service side, but it's still synchronous and you're not using an extra thread. 您必须更改服务端,但它仍然是同步的,并且您没有使用额外的线程。 That changes your server-side interface which can still be shared with the client so it can wait asynchronously. 这会改变您的服务器端接口,该接口仍然可以与客户端共享,因此它可以异步等待。 Otherwise it looks like you have to maintain separate contracts on server and client somehow. 否则看起来你必须以某种方式在服务器和客户端上维护单独的合同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM