1

I understand we should generally avoid async over sync and vice versa. Here are two posts by Stephen Toub which explain this nicely:

We use WCF to communicate across tiers and have a shared contract used both by the service and client. The client uses ChannelFactory<TContract> to create the proxy and channel. One big benefit of this is that we don't have to manually update our [many] service references and we get automatic compile-time checking on both sides if the interface changed. However, it also means we can't keep the server-side interface synchronous (not returning a Task<Result>) and also have the client proxy be asynchronous (return Task<Result> due to the network hop).

So my question is, is there any real harm/downside to have the contract return type Task<Result> and on the server-side just do a return Task.FromResult(SyncCode());? We're effectively doing async over sync on the server side to enable async on the client side, but the network is technically already async anyway. Task.FromResult() shouldn't create any additional threads or introduce any significant overhead. Does this seem like a reasonable exception to the rule?

In other words, I may have an implementation similar to the following:

public PersonResult GetPerson(int id)
{
    return SyncCode();
}

public Task<PersonResult> GetPersonAsync(int id)
{
    return Task.FromResult(GetPerson(id));
}
Nelson Rothermel
  • 9,436
  • 8
  • 62
  • 81

2 Answers2

2

Task.FromResult() shouldn't create any additional threads or introduce any significant overhead. Does this seem like a reasonable exception to the rule?

No, it is not a reasonable exception (if you have a choice), as it hurts scalability of your WCF service. It's in your best interest to use naturally asynchronous APIs server-side, where available.

As to the client, it doesn't matter if your define your WCF contract interface method as PersonResult GetPerson(int id) or Task<PersonResult> GetPersonAsync(int id). The client would work either way, even without recompiling the WCF proxy. For more details:

Asynchrony in the WCF client is completely independent from asynchrony in the WCF service. Usually, you'd want to utilize asynchrony on both sides, but for different reasons: to keep the UI responsive in the client-side app, and to increase the throughput on the server side. For more details:

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • Does `Task.FromResult()` really hurt scalability if the service was synchronous to start with? I understand async or not doesn't affect the message (in the case of HTTP binding, the WSDL is identical), but we were sharing the same contract file on both sides and if the server was synchronous the client couldn't be asynchronous. You can see from my answer a way around that, though it does depend on one operation in both contract files to match so you lose a bit of the automatic compile-time checking. I can probably write tests to at least verify they match during continuous integration. – Nelson Rothermel Apr 19 '15 at 03:05
  • `Task.FromResult` itself doesn't hurt scalability, the synchronous service method it wraps does. Unless such method is purely CPU-bound, it most likely blocks a sever-side thread by calling a synchronous I/O API. If you cannot rewrite it to use an async I/O API instead, it doesn't make sense to return a `Task`. – noseratio Apr 19 '15 at 13:50
  • *but we were sharing the same contract file on both sides and if the server was synchronous the client couldn't be asynchronous* - **why not? the client still can be asynchronous**. You define your contract method as `PersonResult GetPerson(int id)` in the WCF service, and check "generate task-based operations" when you reference the service in the client app. `Task GetPersonAsync(int id)` will be generated for you. The fact that it's asynchronous on the client has nothing to do with the fact that it's synchronous on the server. – noseratio Apr 19 '15 at 14:00
  • *check "generate task-based operations" when you reference the service in the client app* That's the confusion here, we're not adding service references. We're using `ChannelFactory` directly, where `IContract` is referenced both by the service and client sides. We can either change to service references, change the service side to return a Task even though it's synchronous, or create a derived interface that adds tasks just for the client side. The latter seems to be the lesser of all evils in our case. – Nelson Rothermel Apr 20 '15 at 15:49
1

My sample code didn't allow the service to activate since appending Async is considered the same method name. The error was, "Cannot have two operations in the same contract with the same name, methods GetPersonAsync and GetPerson in type ... violate this rule."

From that error message I found a better solution here: https://stackoverflow.com/a/28635558/177333. You have a synchronous interface to implement on server side and create a derived interface that adds async for the client side. Since WCF ignores Async in the name when sending the message across the network, you're calling the same method either way.

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