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).
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:
I can asynchronously call the synchronous service by wrapping the call:
await Task.Run<string>(() => svc.GetTest());
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?
In Windows Communication Foundation (WCF) applications, a service operation can be implemented asynchronously or synchronously without dictating to the client how to call it.
Run the ServiceModel Metadata Utility Tool (Svcutil.exe) tool with both the /async and the /tcv:Version35 command options together as shown in the following command. In the calling application, create a callback method to be called when the asynchronous operation is complete, as shown in the following sample code.
The simplest way to execute a method asynchronously is to start executing the method by calling the delegate's BeginInvoke method, do some work on the main thread, and then call the delegate's EndInvoke method. EndInvoke might block the calling thread because it does not return until the asynchronous call completes.
In synchronous operations tasks are performed one at a time and only when one is completed, the following is unblocked. In other words, you need to wait for a task to finish to move to the next one. In asynchronous operations, on the other hand, you can move to another task before the previous one finishes.
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. 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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With