Reputation: 12423
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
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
Reputation: 660249
My understanding:
GetAPIToken
is called and the current method (GetUser
) returns aTask<User>
object (which will provide a User object later). The current method's execution continues only once theGetAPIToken
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.Task<User>
GetUser
assigns the remainder of GetUser
as the continuation of the Task<Token>
, and suspends by returning the Task<User>
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