1

If a method is marked as async, does that come with any guarantees?

If not, what are the best practices for calling async methods to ensure non-blocking behaviour? (especially when calling a third-party library)


Longer version:

Until now, I assumed that awaiting an async method is guaranteed not to block the thread (if implemented correctly). However, I recently had this situation (also happened to others). In this instance, the (non)blocking behaviour depends on the EF provider. Since the UI layer shouldn't really know or care about the DB provider implementation details, I suppose it makes sense to wrap all calls in Task.Run to ensure non-blocking bahaviour. But then I don't need async anywhere except the UI layer which makes me question the philosophy of having async all the way through.

Community
  • 1
  • 1
loodakrawa
  • 1,468
  • 14
  • 28
  • you have to `await` the call to async method iirc – AD.Net Oct 01 '15 at 01:25
  • This requires a bit of work to fully lay out. But one thing it *doesn't* guarantee, and that's that you won't get bit by side effects of any code you call in or outside of the async methods. –  Oct 01 '15 at 01:29
  • [Here](https://docs.com/paulo-morgado/8860/async-await-general "async-await general") is a set of articles on `async`-`await`. – Paulo Morgado Oct 01 '15 at 06:21

3 Answers3

3

Marking a method async doesn't really come with any guarantees, and certainly does not guarantee that the method will run asynchronously.

What it does do (but I wouldn't call these guarantees):

  • Enables the use of the await keyword inside the method (but does not enforce it!).
  • It does force the return type of the method to be void, Task or Task<TResult>. And in the last 2 cases, it enables the compiler to generate code for you that knows how to set the return value in a Task, or that deals with propagating unhandled exceptions transparently for you.

But for emphasis: it does not guarantee that the method will be coded in an asynchronous way. And you can check this yourself. Write a normal method, mark it as async, and "forget" to await anything inside the method. You should get a warning from the compiler about the method being synchronous, but it will be allowed.

I think you will find it useful to go through the Async/Await FAQ to clear up some of your doubts:

Does using the async keyword on a method force all invocations of that method to be asynchronous?

No. When you invoke a method marked as async, it begins running synchronously on the current thread. So, if you have a synchronous method that returns void and all you do to change it is mark it as async, invocations of that method will still run synchronously. This is true regardless of whether you leave the return type as void or change it to Task. Similarly, if you have a synchronous method that returns some TResult, and all you do is mark it as async and change the return type to be Task<TResult>, invocations of that method will still run synchronously.

Marking a method as “async” does not affect whether the method runs to completion synchronously or asynchronously.

Community
  • 1
  • 1
sstan
  • 35,425
  • 6
  • 48
  • 66
  • So basically I always have to wrap the invocation in Task.Run if I want to be absolutely sure it's going to run asynchronously and not block somewhere deep down. And if I do that there's no benefit of having async anywhere except in the invoking part. – loodakrawa Oct 01 '15 at 03:04
  • 1
    "certainly does not guarantee that the method will run synchronously" Did you mean *asynchronously*? – svick Oct 01 '15 at 07:03
  • 1
    @Luka: No. It just means that you need to do your homework on every async call to make sure it behaves the way you expect it too. Wrapping everything in `Task.Run()` calls blindly would defeat the purpose of `async/await`, which is to provide asynchrony while reducing the number of threads used. – sstan Oct 01 '15 at 12:14
  • @sstan - Your argument makes perfect sense. However, consider the case from the links in my question. IMO, switching EF providers should not affect the design of the UI layer (and switching from dev Compact to prod Server is a real world scenario). How would you suggest approaching this situation? – loodakrawa Oct 01 '15 at 23:11
2

I suppose it makes sense to wrap all calls in Task.Run to ensure non-blocking bahaviour

I don't think it does. While you indeed have no guarantees, most async methods will be actually asynchronous. So I think you should wrap the calls in Task.Run() only after you figure out that a method (or some implementation of it) is actually blocking, but not before.

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

If a method is marked as async, does that come with any guarantees?

No, as others have said, it makes no guarantee as to the actual operation being asynchronous. It is merely a hint for you to understand that there may be an asynchronous operation. You are mainly relying on the people implementing those async methods to actually expose true async operations, and not lie to you with "fake asynchrony" using Task.Run, etc.

For example, this method is Task returning:

public Task<string> DoHeavyLiftingAsync()
{
    var tcs = new TaskCompletionSource<string>();
    string result = SomeHeavyWork();
    return tcs.SetResult(result);
}

Basically, no asynchronous operations were performed. Could this happen? Yes, if either the person implementing it doesn't realize what he's guaranteeing to the caller, this may happen.

If not, what are the best practices for calling async methods to ensure non-blocking behaviour? (especially when calling a third-party library?

Generally, you can trust must async implementations in the BCL to provide you true asynchrony (although, you may be surprised at times). If you're using a third-party API, you can always look at the code using a .NET decompiler, to see what's actually going on behind the scenes. If after doing that, you're still not sure, you can always wrap the call with Task.Run. Make sure you use that as a last resort.

Community
  • 1
  • 1
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321