17

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?

Community
  • 1
  • 1
poke
  • 369,085
  • 72
  • 557
  • 602
  • If your underlying operations are synchronous, there's really no point in trying to make the whole application asynchronous. Just do everything synchronously. If/when you eventually get actually asynchronous IO under the hood *then* transition the app into an asynchronous one. – Servy Mar 25 '14 at 15:19
  • 1
    @Servy The *client* is on a different machine, consuming the services over the network. Of course it makes sense to have the client fully asynchronous, let alone to have no blocking UI. – poke Mar 25 '14 at 15:22
  • If the client needs to perform a series of synchronous operations asynchronously, for example to keep the UI from being blocked, they they can wrap those synchronous methods in a call to `Run`. That doesn't change the fact that your API should expose the methods synchronously, because that's what they are. That's exactly what Stephen explained. – Servy Mar 25 '14 at 15:25
  • @Servy That’s exactly my question, whether I should wrap it on the server- or on the client-side (see the very last sentence). But I disagree. Even if the server does the work synchronously, that does not mean that the service call needs to be synchronous. E.g. if the server takes a few seconds to finish its work before it can return, then consuming the service asynchronously makes a lot of sense. This is also happens when generating an async proxy with VS or svcutil. And wrapping it on the server-side would provide actual asynchronous services. – poke Mar 25 '14 at 15:34
  • The asynchrony of the server is completely independent from the asynchrony of the client. The auto-generated proxies can create asynchronous APIs even if the server is synchronous; you should be able to do the same with your custom proxies. – Stephen Cleary Mar 25 '14 at 15:36
  • 3
    If you're calling `Task.Run` around synchronous methods for your API, you're probably doing something wrong. Having a network request from a client to a server be asynchronous, when the server does the processing synchronously, is fine. When doing that, the asynchronous is around the network request; and that network request doesn't *care* if the server method is asynchronous or synchronous. – Servy Mar 25 '14 at 15:36
  • 1
    @StephenCleary I’m not using proxies though, I use client factories, so I don’t automatically have that option. I will have to add the async-capabilites myself. – poke Mar 25 '14 at 15:38
  • 1
    @Servy That’s exactly why I’m asking this question. The server does its stuff synchronously, and—as its client—I want to be able to consume the services asynchronously. But I don’t know how to do that correctly. – poke Mar 25 '14 at 15:41
  • @poke: To clarify, is the asynchronous code you're writing going to be running in WCF, or on some other framework? – StriplingWarrior Mar 25 '14 at 15:45
  • @StriplingWarrior The client which wants to consume the WCF services asynchronously will be using WPF, but that shouldn’t really matter much. The WCF server itself currently only works synchronously. – poke Mar 25 '14 at 15:47
  • @poke And the client, when generating their client side API, has an option to generate an asynchronous API rather than a synchronous API. How the server is implemented is irrelevant to this decision. The server should implement the methods with no regard for whether the clients are synchronous or asynchronous. – Servy Mar 25 '14 at 16:01
  • 2
    @Servy I’m not using proxies though, so I can’t auto-generate such an asynchronous API… – poke Mar 25 '14 at 16:10
  • @poke: Actually, it does matter much, because as I mentioned in my post, WCF has special behavior around task scheduling, which you don't have to worry about in WPF. It also means that this question is not really about WCF at all, since the same question would apply equally to any client code consuming a web service, right? – StriplingWarrior Mar 25 '14 at 17:35
  • I have rewritten the whole question, and also prepared an example solution for download. I hope my intention is a bit more clear now. I’m also in chat in case there are additional clarification questions. – poke Mar 25 '14 at 17:45
  • @StriplingWarrior It’s more the other way around. The question is not about WPF, because *any* kind of client could want to asynchronously consume the WCF service. – poke Mar 25 '14 at 17:56
  • 1
    Personally I'd rather have a single thread on 100 clients waiting for a response than 100 threads on a server doing it. All operations with databases/devices etc. are synchronous at some point. So if you extend your service contract with async methods, it just means you're shifting work from the clients to the server. RE your question, you should be able to just do an await on Task.Factory.StartNew() I think... – HiredMind Mar 25 '14 at 18:06
  • 1
    @poke: Thanks for clarifying. Good rewrite. I don't have time to look into it further right now, but I am curious to see if you get a good response. – StriplingWarrior Mar 25 '14 at 18:07
  • “I'm not using proxies” Why not? Can't you start using them? – svick Mar 25 '14 at 19:59
  • 1
    @svick There are multiple reasons. Mostly to avoid the additional build dependency, but also to enable code reuse, have more control over the code base and to increase maintainability due to the amount of services we have. See also [this article](http://jeff.polkspot.net/blogs/jeff/post/2010/01/29/Avoiding-Visual-Studio-Service-Reference.aspx). – poke Mar 26 '14 at 14:50

3 Answers3

7

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();
    }
}
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • 3
    +1, besides one point: *" You should have your sync function on your sever and only the sync function on your server.*". I'd say, if the method can be done as *naturally* async on the server (rather than a `Task.Run` wrapper), have only the *async* method on the server. The generated WSDL is the same for sync and async contracts, I just found out that: http://stackoverflow.com/q/22623922/1768303 – noseratio Mar 25 '14 at 21:39
  • 1
    Unfortunately, one of the main reasons to use channel factories was to be able to share the same interface on either side. But this is really interesting; just having a TAP-enabled interface is enough to make the channel factories asynchronous. I might be able to (“hackishly”) use this to proxy the calls myself in some way if I don’t find a better solution. – poke Mar 26 '14 at 13:45
  • @poke: Did you ever find a good solution for this? When you control both client/server it's very nice to be able to share the interface, but I also agree it would be nice to be able to do async on the client. I hope they're not mutually exclusive, though I can't see how you could use the service's non-task signature when you want a task signature on the client. Your client proxy could transparently do that, but that defeats the whole purpose since your code that uses the proxy wouldn't know it's a task and couldn't await, etc. – Nelson Rothermel Sep 25 '14 at 21:07
  • @poke: Just brainstorming, but maybe you could auto-generate an async version of the interface using T4 templates. As long as your Action and ReplyAction namespaces are predictable, that shouldn't be too hard. – Nelson Rothermel Sep 25 '14 at 21:12
  • @NelsonRothermel Normally when working with WCF I just use [svcutil.exe](http://msdn.microsoft.com/en-us/library/aa347733(v=vs.110).aspx) to generate the proxy for me. It makes them as partial classes so I can write my own stuff to extend it without modifying the autogenerated file. You can set it up to run svctuil as an after build event in the service project and have it update your other projects. (or use the built in GUI tools to build the proxy, but I find scripting svctuil is easier to maintain than editing the `Reference.svcmap` file for the GUI version.) – Scott Chamberlain Sep 25 '14 at 21:15
  • @NelsonRothermel Not really, no. I ended up calling the services synchronously in a small asynchronous wrapper as there wasn’t a high priority at that point. I have since stopped working on the project. What I did for test purposes though was create something that emits an asynchronous interface for a synchronous contract (allowing for asynchronous access) and then a utility to call the asynchronous method via reflection (by making the synchronous call in a lamda expression). It worked fine, but it’s really as hackish as it sounds… ^^ – poke Sep 25 '14 at 21:19
  • @poke True, reflection would let you get around the limitation I mentioned: your code that uses the proxy wouldn't know it's a task and couldn't await. Anyway, thanks for the update. – Nelson Rothermel Sep 28 '14 at 01:24
  • @NelsonRothermel Well, I had a method like `svc.CallAsync(s => s.SynchronousCall(params));` that would then look up `SynchronousCallAsync` in the run-time emitted interface and call that via reflection. That way, I would make it a real asynchronous call and would get a Task back. – poke Sep 28 '14 at 01:27
7

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. Otherwise, just leave it synchronous (but don't use 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. Your clients will keep running unchanged. By making your server-side WCF method asynchronous, all you do is improve the service scalability. 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.

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

In your test project, you're trying to exposes different versions of the same API under different contract names:

[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:

[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. To sum up, it is not like the WCF service calls back your client to notify about the completion of the async operation. Rather, it simply sends back the full SOAP response using the underlying network protocol when it's done. 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.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 2
    *“it is not like the WCF service calls back your client”* I totally didn’t think about that, thanks a lot! Btw. I named the methods poorly in the example project. I added the explicit names because `GetTest` and `GetTestAsync` were conflicting each other (further proving what you said)—I should have just named them just Test1, Test2 and Test3 instead (the idea was to just show the different implmentation possibilities). – Anyway, do you know if it’s possible to use non-blocking/async TCP calls when using channel factories, or can I only access those with generated proxies? – poke Mar 26 '14 at 13:40
  • 2
    @poke, I'm far from being an expert here, but I think the generated proxies use channel factories behind the scene, you may want to look at the generated code. So it should be possible. You may want to ask this as a separate question. BTW, note that in WSDL metadata there will just `string Test()` method, even if you declare it in the contract as `Task TestAsync()`. That again confirms the fact that asynchrony on the server and on the client for a given method are totally independent. – noseratio Mar 26 '14 at 13:50
2

This isn't horrible: https://stackoverflow.com/a/23148549/177333. You just wrap your return values with 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.

Community
  • 1
  • 1
Nelson Rothermel
  • 9,436
  • 8
  • 62
  • 81