What the async and await keywords apply to?

Viewed 124

I'm completely confused.

It says here:

Async methods can have the following return types:

  • Question 1. Why write about tasks if Task and Task<Result> fit the "GetAwaiter" criterion?

  • Question 2. Here I got the answer that the IAsyncEnumerable<T> point is more about await. Then what about everything else? Can someone clearly explain what each keyword applies to without big examples?

    What types can return async methods - list or criteria? And what types can the await keyword or their criteria apply to?

Examples of answers how I would like:


Keyword async applicable for return values (async methods can return values): type1, type2, typeN, or any type with specific characteristics (for example ... some characteristics).

Keyword await applicable for awaitable types: type1, type2, typeN, or type with specific characteristics (for example contains GetAwaiter).


  • Question 3. Speaking of void we mean only the return value of the async method. Can void be the awaitable type? Can someone give an example of a void awaiting?
1 Answers

Why write about tasks if Task and Task fit the "GetAwaiter" criterion?

Probably because Task/Task<Result> will be used in 90% or so of all cases. It hints to a new developer to just use these types, and not try to implement a custom type for this, or spending extra time looking up the documentation.

Question 2. Here I got the answer that the IAsyncEnumerable point is more about await. Then what about everything else? Can someone clearly explain what each keyword applies to without ten-story examples?

async is used to tell the compiler to rewrite the method into a state machine, so that different parts of the method can be run at different times. If the method returns anything, this needs to be wrapped in something that allow the caller to use the result when it is actually produced.

await is used in an async method suspend execution of the method until the awaited value is available. I.e. it marks where the compiler will split the method into the different sections.

I think you are making this more complicated then it needs to be:

  • Use Task when you do not need to return a value
  • Use Task<Result> when you return a value
  • Use IAsyncEnumerable<T> when producing a sequence of values asynchronously. I.e. each value is produced by a separate network/Database call. Contrast with Task<T[]> when one IO call produces a sequence of values.
  • Use ValueTask when you want to minimize overhead. See Understanding the Whys, Whats, and Whens of ValueTask
  • Use void when you are forced to. I.e. UI event handlers.
  • Use a custom type when you have some special reason to. This should be quite rare, I would expect 99% of developers never to write any such implementation.

Speaking of void we mean only the return value of the async method, is void not the expected type?

A normal non-async void method can actually produce two results:

  1. Nothing
  2. An exception

And the result is only produced once the method has reached its end. Awaiting a method returning a Task replicates this behavior. An async void method however does not. Since there is nothing to await, the caller have no way of knowing when, and if the method reaches its end. And exceptions may be lost, without anyone knowing it occurred. This is acceptable for things like UI events, after all, why does the UI system does not really care about what happens when a button is pressed, it is up to the developer to do things like handling exceptions. But in most other cases, Task should be used to represent a synchronous void-method.

Related