When is the best place to use Task.Result instead of awaiting Task

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 of Foo, and eventually completing the task representing the workflow of Foo.

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.

Leave a Comment