Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
283 views
in Technique[技术] by (71.8m points)

c# - 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. The application is a client for a WCF service which currently offers only synchronous services. 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. As such, I cannot use the auto-generation from VisualStudio or svcutil to generate asynchronous client proxies.

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. 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. 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.

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).

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. 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).

// 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. Option 2 waits on the server by wrapping the synchronous method there.

What would be the recommended way to make a synchronous WCF service async-aware? 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, i.e. by introducing “real” asynchronicity on the connection—like the generated proxies do?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...