Let’s make sure to not bury the lede here:
So for example: [some correct code] becomes [some incorrect code]
NEVER NEVER NEVER DO THIS.
Your instinct that you can restructure your control flow to improve performance is excellent and correct. Using Result
to do so is WRONG WRONG WRONG.
The correct way to rewrite your code is
var userTask = _userRepo.GetByUsername(User.Identity.Name);
//Some work that doesn't rely on the user object
user = await _userRepo.UpdateLastAccessed(await userTask, DateTime.Now);
return user;
Remember, await does not make a call asynchronous. Await simply means “if the result of this task is not yet available, go do something else and come back here after it is available”. The call is already asynchronous: it returns a task.
People seem to think that await
has the semantics of a co-call; it does not. Rather, await is the extract operation on the task comonad; it is an operator on tasks, not call expressions. You normally see it on method calls simply because it is a common pattern to abstract away an async operation as a method. The returned task is the thing that is awaited, not the call.
However, things I’ve seen posted imply that result should be used rarely and await is preferred but I don’t understand why I’d want to wait for my user object to be fetched if I can be performing some other independent logic at the same time?
Why do you believe that using Result
will allow you to perform other independent logic at the same time??? Result prevents you from doing exactly that. Result is a synchronous wait. Your thread cannot be doing any other work while it is synchronously waiting for the task to complete. Use an asynchronous wait to improve efficiency. Remember, await
simply means “this workflow cannot progress further until this task is completed, so if it is not complete, find more work to do and come back later”. A too-early await
can, as you note, make for an inefficient workflow because sometimes the workflow can progress even if the task is not complete.
By all means, move around where the awaits happen to improve efficiency of your workflow, but never never never change them into Result
. You have some deep misunderstanding of how asynchronous workflows work if you believe that using Result
will ever improve efficiency of parallelism in the workflow. Examine your beliefs and see if you can figure out which one is giving you this incorrect intuition.
The reason why you must never use Result
like this is not just because it is inefficient to synchronously wait when you have an asynchronous workflow underway. It will eventually hang your process. Consider the following workflow:
task1
represents a job that will be scheduled to execute on this thread in the future and produce a result.- asynchronous function Foo awaits task1.
- task1 is not yet complete, so Foo returns, allowing this thread to run more work. Foo returns a task representing its workflow, and signs up completing that task as the completion of
task1
. - The thread is now free to do work in the future, including
task1
. task1
completes, triggering the execution of the completion of the workflow ofFoo
, and eventually completing the task representing the workflow ofFoo
.
Now suppose Foo
instead fetches Result
of task1
. What happens? Foo
synchronously waits for task1
to complete, which is waiting for the current thread to become available, which never happens because we’re in a synchronous wait. Calling Result causes a thread to deadlock with itself if the task is somehow affinitized to the current thread. You can now make deadlocks involving no locks and only one thread! Don’t do this.