2

The C# compiler already warns us if we have a method that has the async modifier but does not use the await operator.

According to this answer there's no point adding an await to the end of an async method (and in that case, just remove the async modifier).

But what if the method has an expensive synchronous operation it needs to perform before calling a subsequent true async method?

For example, if I'm using HttpClient:

private readonly HttpClient client = ...

public Task<HttpResponseMessage> CallMyWebServiceMethod() {

    HttpRequestMessage request = this.SomeExpensiveButSynchronousMethod();

    return this.client.SendAsync( request );
}

This code would block the caller (due to SomeExpensiveButSynchronousMethod).

However, if I change the code to this:

public async Task<HttpResponseMessage> CallMyWebServiceMethod() {

    HttpRequestMessage request = this.SomeExpensiveButSynchronousMethod();

    return await this.client.SendAsync( request );
}

and I call it like so:

HttpResponse response = await myWrapper.CallMyWebServiceMethod();

...I understand the TPL would spin up a background thread immediately and then run CallMyWebServiceMethod in the background thread, resuming whatever the parent code wants, making the entire call non-blocking in the process, before resuming after the Task completes and returns the HttpResponse.

...if that's the case then it seems contradictory.

If I'm wrong, and the call is blocking until it gets to SendAsync then how can I execute SomeExpensiveButSynchronousMethod on the same background thread as the HttpClient uses for its request?

Community
  • 1
  • 1
Dai
  • 141,631
  • 28
  • 261
  • 374
  • This is really more implementation specific. Do you want the request to be asynchronous or synchronous(blocking)? – Tdorno Mar 30 '17 at 23:10
  • @Tdorno I want to wrap `HttpClient` with my own operations in a way that "preserves" async-ness - assuming that's what we're supposed to do. – Dai Mar 30 '17 at 23:15

3 Answers3

3

According to this answer there's no point adding an await to the end of an async method (and in that case, just remove the async modifier).

That's an oversimplification. See my blog post on eliding async and await.

But what if the method has an expensive synchronous operation it needs to perform before calling a subsequent true async method?

This is a rare case, but the appropriate solution IMO is to execute the synchronous operation synchronously (i.e., not wrapped in a Task.Run) and be sure to document its behavior.

I understand the TPL would spin up a background thread immediately and then run CallMyWebServiceMethod in the background thread, resuming whatever the parent code wants, making the entire call non-blocking in the process, before resuming after the Task completes and returns the HttpResponse.

That is not at all what happens. You may find my async intro helpful. Quote:

The beginning of an async method is executed just like any other method. That is, it runs synchronously until it hits an “await” (or throws an exception).

In actuality, both of your examples synchronously block the caller while executing SomeExpensiveButSynchronousMethod.

If I'm wrong, and the call is blocking until it gets to SendAsync then how can I execute SomeExpensiveButSynchronousMethod on the same background thread as the HttpClient uses for its request?

HttpClient doesn't use a background thread for its request, so this part of the question doesn't make sense. For more information on how asynchronous I/O works, see my blog post There Is No Thread.

To answer the actual question:

Should I add the async modifier if I return a Task in an expensive method?

Yes. But the reason you should do so is not to "make it asynchronous"; it's so that any exceptions from SomeExpensiveButSynchronousMethod are captured and placed on the returned Task, which is the expected semantic for methods that follow the Task-based Asynchronous Pattern (TAP).

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
1

I understand the TPL would spin up a background thread immediately and then run CallMyWebServiceMethod

No, that's not what happens. The first synchronous part of an async method executes synchronously. If you want make sure it does not block the current thread, you should use Task.Run().

svick
  • 236,525
  • 50
  • 385
  • 514
0

Your expensive method is going to block the caller either way. async doesn't magically create threads for you, that's what Task does (sometimes). So, if you add async and await the SendAsync, you're just adding state-machine overhead needlessly.

see also http://blog.stephencleary.com/2016/12/eliding-async-await.html

Peter Ritchie
  • 35,463
  • 9
  • 80
  • 98