Await on a Completed Task Same as Task.Result

Await on a completed task same as task.Result?

There are already some good answers/comments here, but just to chime in...

There are two reasons why I prefer await over Result (or Wait). The first is that the error handling is different; await does not wrap the exception in an AggregateException. Ideally, asynchronous code should never have to deal with AggregateException at all, unless it specifically wants to.

The second reason is a little more subtle. As I describe on my blog (and in the book), Result/Wait can cause deadlocks, and can cause even more subtle deadlocks when used in an async method. So, when I'm reading through code and I see a Result or Wait, that's an immediate warning flag. The Result/Wait is only correct if you're absolutely sure that the task is already completed. Not only is this hard to see at a glance (in real-world code), but it's also more brittle to code changes.

That's not to say that Result/Wait should never be used. I follow these guidelines in my own code:

  1. Asynchronous code in an application can only use await.
  2. Asynchronous utility code (in a library) can occasionally use Result/Wait if the code really calls for it. Such usage should probably have comments.
  3. Parallel task code can use Result and Wait.

Note that (1) is by far the common case, hence my tendency to use await everywhere and treat the other cases as exceptions to the general rule.

Await vs Task.Result in an Async Method

await asynchronously unwraps the result of your task, whereas just using Result would block until the task had completed.

See this explanantion from Jon Skeet.

Task TResult .Result vs await a task


First version

static void Caller()
{
Task<int> test = TestMethod();
int k = test.Result;
Console.WriteLine("k: " + k);
}

In this version the async keyword would be obsolete. This is a synchronous method. The executing thread blocks at test.Result until the task has finished.

Second version

static async Task Caller()
{
Task<int> test = TestMethod();
int i = await test;
Console.WriteLine("i: " + i);
}

This is a (kind of) asynchronous version (it's not really asynchron, it's just a synchronous method run on a different thread). The difference to the first version is that the compiler builds a state machine for this.

So the control flow of the executing thread is returned to the caller of this method when await test is reached.

When the task has finished, the execution of this method is resumed at Console.WriteLine (or more precisely at the assignment to i).

For more information about what the compiler does you can read for example this.


The second version is useful if you have a longer running task that you need to execute from an UI. Since the control is returned to the caller while awaiting the task, the UI thread is not blocked and your application stays responsive.

After Task.IsCompleted what is better: await or Result

Do not use .Result, always use await.

Please note that .Result can cause deadlocks and can cause some unpredictable behaviors in the applications.

The only way then would be to take the process dump and then analyze dump in procdump. Believe me it is going to be very difficult debugging.

Please find best practices about async programming at this blog.

As far as exception handling is concerned, it is mentioned in this blog that:

Every Task will store a list of exceptions. When you await a Task, the
first exception is re-thrown, so you can catch the specific exception
type (such as InvalidOperationException). However, when you
synchronously block on a Task using Task.Wait or Task.Result, all of
the exceptions are wrapped in an AggregateException and thrown.

Hope this helps.

await Task.CompletedTask vs return

Let's look the question from the consumer-side.

If you define an interface, which imposes an operation that returns a Task then you don't say anything about how it will be calculated / executed (so, there is no async access modifier in the method signature). It is an implementation detail.

Interface

public interface ITest
{
Task Test();
Task<bool> IsTest();
}

So, it is up to you how you implement the interface.

You can do it in a synchronous way, when there won't be any AsyncStateMachine generated because of the absence of the async keyword.

Implementation #1

public class TestImpl : ITest
{
public Task Test()
{
return Task.CompletedTask;
}

public Task<bool> IsTest()
{
return Task.FromResult(true);
}
}

Or you can try to implement it in an asynchronous way but without await operators. Here you will receive CS1998 warnings.

Implementation #2

public class TestImpl : ITest
{
public async Task Test()
{
return;
}

public async Task<bool> IsTest()
{
return true;
}
}

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

In other words this implementation does not define a state machine. An async method is divided into different states based on the await keywords:

  • before_the_await_1,
  • after_the_await_1_but_before_the_await_2
  • after_the_await_2_but_before_the_await_3
  • ...

If you haven't got any await then you would have a single state, which would run in sync (there is no need to preserve state, execute async operation and then call MoveNext()).


Or you can try to implement it in an asynchronous way with await operators.

Implementation #3

public class TestImpl : ITest
{
public async Task Test()
{
await Task.CompletedTask;
}

public async Task<bool> IsTest()
{
return await Task.FromResult(true);
}
}

In this case there will be an async state machine but it won't be allocated on the heap. By default it is a struct and if it finishes in sync then we don't need to allocate them on the heap to extend its life out of the scope of the method.

For further information please read these articles:

  • Async/await in .NET Core 3.x
  • Async ValueTask Pooling in .NET 5
  • Dissecting the async methods in C#

Awaiting completed tasks in async methods

This can easily be tested:

        var t1 = ExecuteActionAsync();
Console.WriteLine($"Returned task: {t1.IsCompleted}");
await t1;
Console.WriteLine($"Awaited task: {t1.IsCompleted}\n");

var t2 = MyMethod();
Console.WriteLine($"Returned task: {t2.IsCompleted}");
await t2;
Console.WriteLine($"Awaited task: {t2.IsCompleted}\n");

t1 = ExecuteActionAsync();
t2 = MyMethod();

var t3 = Task.WhenAll(new []{t1,t2 });
Console.WriteLine($"Task from WhenAll: {t3.IsCompleted}");

Result:

Returned task: True
Awaited task: True

Returned task: True
Awaited task: True

Task from WhenAll: True

So: Yes, if the async method runs synchronously, the returned Task is completed. And yes, the Task returned from WhenAll passing async methods that run synchronously is also completed.

Stephen Cleary wrote a usefull blog on this subject. Specifically the following quote is relevant:

The beginning of an async method is executed just like any other method. That is, it runs synchronously until it hits an “await” (or throws an exception).

The “await” keyword is where things can get asynchronous. Await is like a unary operator: it takes a single argument, an awaitable (an “awaitable” is an asynchronous operation). Await examines that awaitable to see if it has already completed; if the awaitable has already completed, then the method just continues running (synchronously, just like a regular method).

Approaching the question form the specifications side:

  • an async method must return an 'awaitable' type (e.g. 'Task'), which must have an 'IsCompleted' property
  • "the purpose of the IsCompleted property is to determine if the task is already complete. If so, there is no need to suspend evaluation."

From these statements, it can be concluded that an async method that runs synchronously, will indeed return a completed Task.

Even if an exception is thrown while executing the async method, the returned Task will be completed ('IsCompleted' will return 'true' though 'IsCompletedSuccessfully' will return 'false').

.Result or await after Task.WhenAll

There's no problem, as the tasks have already finished. This is a false positive which can be ignored.

If all tasks return the same result though, Task.WhenAll returns an array with the results:

var results=await Task.WhenAll(tasks);

The results are in the same order as the tasks that produced them

What happens when awaiting on already-completed task?


The method that handles the user's request awaits that Task, would it get the value immediately?

Yes. You can think of it as being lazy, if you await a task that is already completed it returns immediately. You could await it several times on different threads and it would only return once it has the result (or is faulted).

Task.CompletedTask was added as a nicety for this very reason. You could await this and it would immediately return a successful task as it has already been completed.

Does Task.Result have any impact when used after the await statement?


Would Task.Result cause any issues [if the task is already completed]?

Task<T>.Result will synchronously wait for the task to complete (the same as task.Wait()), and then return the result (the same as await task).

Since your task is already completed, there is only one other difference between Result and await that is important: Result will wrap exceptions in an AggregateException. For this reason (and also to make the code more refactor-safe), I use await instead of Result whenever possible. That is, take the DoSomethingAsync2 approach.

I've read a bit about some blocking and deadlock issues that Task.Result can cause, but I think it wouldn't in this case, since it runs on a separate task and there is an await on the task which already ensures the Result.

It wouldn't because it's running in the thread pool context.

So, this is something I'm planning to do in a step-by-step way, starting with operations that doesn't depend on main thread.

You may find my brownfield async article helpful. In that article, I refer to this technique as "The Thread Pool Hack".

Are there any hidden bottlenecks/issues that I need to be aware of in the below scenario?

A few:

You shouldn't use StartNew. Use Task.Run instead.

You shouldn't fire-and-forget on ASP.NET (i.e., call an asynchronous method without consuming its task).

You shouldn't expose fake-asynchronous methods (i.e., methods with an asynchronous signature but that are implemented by blocking a thread pool thread). Instead of using Task.Run to implement a method, use Task.Run to call a method.

Combining those three points, the proper structure of The Thread Pool Hack is to use Task.Run with GetAwaiter().GetResult() (which is just like Result but avoids the AggregateException wrapper):

public ActionResult Index()
{
var r = Task.Run(() => LongRunner.LongRunnerInstance.DoSomethingThatTakesReallyLong())
.GetAwaiter().GetResult();
return View();
}

As a final issue, as long as you have this pattern in your ASP.NET application, the scalability of your application will be decreased. Instead of using one thread during DoSomethingThatTakesReallyLong, your app is now using two; this may also throw off the ASP.NET thread pool heuristics. This may be acceptable during the transition to async, but keep it in mind as a motivator to finish the transition.



Related Topics



Leave a reply



Submit