I've recently been learning asynchronous programming and I think I've mastered it.
I was one of the designers of the feature and I don't feel like I've even come close to mastering it, and you are asking beginner level questions and have some very, very wrong ideas, so there's some hubris going on here I suspect.
Asynchronous programming is simply just allowing our program to multitask.
Suppose you asked "why are some substances hard and some soft?" and I answered "substances are made of arrangements of atoms, and some atom arrangements are hard and some are soft". Though that is undoubtedly true, I hope you would push back on this unhelpful non-explanation.
Similarly, you've just replaced the vague word "asynchronous" with another vague word "multitask". This is an explanation that explains nothing, since you haven't clearly defined what it means to multitask.
Asynchronous workflows are undoubtedly about executing multiple tasks. That's why the fundamental unit of work in a workflow is the Task<T>
monad. An asynchronous workflow is the composition of multiple tasks by constructing a graph of dependency relationships among them. But that says nothing about how that workflow is actually realized in software. This is a complex and deep subject.
I don't see the async keyword as much, just something you chuck on a method to let Visual Studio know that the method may await something and for you to allow it to warn you.
That's basically correct, though don't think of it as telling Visual Studio; VS doesn't care. It's the C# compiler that you're telling.
If it has some other special meaning that actually affects something, could someone explain?
It just makes await
a keyword inside the method, and puts restrictions on the return type, and changes the meaning of return
to "signal that the task associated with this invocation is complete", and a few other housekeeping details.
await doesn't block the current method
Of course it does. Why would you suppose that it does not?
It doesn't block the thread, but it surely blocks the method.
it simply executes the code left in that method and does the asynchronous operation in its own time.
ABSOLUTELY NOT. This is completely backwards. await
does the opposite of that. Await means if the task is not complete then return to your caller, and sign up the remainder of this method as the continuation of the task.
As you can see, I run some asynchronous code on GetPlayerAsync, what happens if we get deeper into the scope and we need to access player, but it hasn't returned the player yet?
That doesn't ever happen.
If the value assigned to player
is not available when the await
executes then the await returns, and the remainder of the method is resumed when the value is available (or when the task completes exceptionally.)
Remember, await mean asynchronously wait, that's why we called it "await". An await is a point in an asynchronous workflow where the workflow cannot proceed until the awaited task is complete. That is the opposite of how you are describing await.
Again, remember what an asynchronous workflow is: it is a collection of tasks where those tasks have dependencies upon each other. We express that one task has a dependency upon the completion of another task by placing an await
at the point of the dependency.
Let's look at your workflow in more detail:
var player = await GetPlayerAsync();
foreach (var uPlayer in Players.Values) ...
if (player.Username == "SomeUsername") ...
The await means "the remainder of this workflow cannot continue until the player is obtained". Is that actually correct? If you want the foreach
to not execute until the player is fetched, then this is correct. But the foreach doesn't depend on the player, so we could rewrite this like this:
Task<Player> playerTask = GetPlayerAsync();
foreach (var uPlayer in Players.Values) ...
Player player = await playerTask;
if (player.Username == "SomeUsername") ...
See, we have moved the point of dependency to later in the workflow. We start the "get a player" task, then we do the foreach, and then we check to see if the player is available right before we need it.
If you have the belief that await
somehow "takes a call and makes it asynchronous", this should dispel that belief. await
takes a task and returns if it is not complete. If it is complete, then it extracts the value of that task and continues. The "get a player" operation is already asynchronous, await
does not make it so.
If it doesn't block the method, how does it know that player isn't null
It does block the method, or more accurately, it suspends the method.
The method suspends and does not resume until the task is complete and the value is extracted.
It doesn't block the thread. It returns, so that the caller can keep on doing work in a different workflow. When the task is complete, the continuation will be scheduled onto the current context and the method will resume.