Matt W
Matt W

Reputation: 12423

What is await doing here?

Given the method signature:

(awaitable) Task<Token> ITokenService.GetAPIToken();

In this method what is happening on the line with the await?

public async Task<User> GetUser(string userId)
{
    Token token = await TokenService.GetAPIToken();

    //..........
}

My understanding: GetAPIToken is called and the current method (GetUser) returns a Task<User> object (which will provide a User object later). The current method's execution continues only once the GetAPIToken method returns.

If I am wrong, what is the type/value of token while the code waits for the GetAPIToken() method to return?

I understand that the result of calling this method will be different given that it can be called either with or without the await keyword. Please assume, for the post, that it is being called with await.

Upvotes: 1

Views: 282

Answers (2)

J.P.
J.P.

Reputation: 380

On the line with the await, indeed the execution of GetUser() is stopped (awaits) until GetAPIToken() returns. This is correct. Therefore token has no value (or is even declared in this case) until it gets it from GetAPIToken() returning.

What GetUser() returns depends on how it's called, with or without the await operator. If called with await, the caller of GetUser() will wait for it to finish as well. Also in this case the caller will eventually get a User returned by GetUser(), not a Task<User>.

In this scenario, you're not benefiting by running anything in parallel.

If on the other hand GetUser() is called without the await operator, two things happen differently: GetUser() returns to the caller earlier, as soon as it has hit your line actually; and it returns a Task<User> instead of a User directly -- after all none had yet been created.

From this Task the caller can later (after doing other things in parallel, that'd be the whole point) get the resulting User. This can be done in different ways -- the most idiomatic being to await it then; or else see the methods and properties of Task<>.

It is important to understand when things run in parallel. In that case (if GetUser() were called without await) it's a possibility that GetUser() returns and you continue doing stuff while, in parallel in the other thread, GetAPIToken() hasn't yet returned and so token is not initialized.

Check this nice MSDN article with graph: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/index#BKMK_WhatHappensUnderstandinganAsyncMethod

Upvotes: 1

Eric Lippert
Eric Lippert

Reputation: 660249

My understanding: GetAPIToken is called and the current method (GetUser) returns a Task<User> object (which will provide a User object later). The current method's execution continues only once the GetAPIToken method returns.

That is very close. Let's crisp that up in two ways.

First, let's make a distinction between returns -- which means, returns control to the caller -- and completes, which means the task represented by the method is completed and either we have its value, or, in the case of abnormal completion, an exception.

In non-async methods we do not need to make a distinction because returning control to the caller only happens on completion. In async methods, we can return-to-caller-as-suspension (because of awaiting a non-completed task) or return-as-completion (when the represented task is signalled as being complete).

Second, it is helpful to express the workflow in terms of what happens if the task is complete when it is created; this can happen in the case of caching, say.

So let's now re-state your understanding with that in mind:

  • GetAPIToken is called and returns a task to the current user.
  • If the task is completed, then GetApiToken returned when it finished all its work. GetUser extracts the token from the completed task and continues without suspending.
  • If the task is not completed, and this is the first await in GetUser, then GetUser creates a Task<User>
  • GetUser assigns the remainder of GetUser as the continuation of the Task<Token>, and suspends by returning the Task<User>
  • When the Task<Token> completes it runs its continuation, which resumes GetUser where it left off.

what is the type/value of token while the code waits for the GetAPIToken() method to return?

Well, what would the value of token be in an equivalent non-async case? Consider:

Token x = Foo();

What's the value of x while we're waiting for Foo to complete? Making Foo async doesn't make a difference; the local is not assigned a value until after the call completes normally. If Foo goes into an infinite loop, or if Foo throws, then x is never assigned.

In practice of course, C# assigns all locals their default value when they are created, so that's the value that will be in the variable.

Upvotes: 7

Related Questions