johni
johni

Reputation: 5568

What happens when awaiting on already-completed task?

When I construct an instance of a class that I have, I would like to trigger a Token renewal function (async method) and let it run in the background (I keep a reference to the returned Task).

Later on, when a user triggers a request, I would like to await on that Task.

Lets assume that the Task completes after 1 second, and that the user triggers a request after 2 seconds (which means, the Task is completed).

The method that handles the user's request awaits that Task, would it get the value immediately? after all, the Task is completed and holds the value.

Upvotes: 26

Views: 12893

Answers (4)

Dave Black
Dave Black

Reputation: 8019

Though the OP didn't mention ValueTask, I'm adding this answer to include ValueTask since it has been added after this question was originally asked.

  • With regard to Task and Task<T>, as others have noted, you can await a Task (or completed Task) multiple times. If the task has run to completion, it returns immediately.
  • However, if you are using a ValueTask or ValueTask<T>, you cannot await it more than once. ValueTask and ValueTask<T> were introduced in .NET Core 2.0 and are used to avoid/minimize memory allocations for performance-sensitive areas.

As taken from Stephen Toub on the Dotnet Blog:

However, because ValueTask and ValueTask may wrap reusable objects, there are actually significant constraints on their consumption when compared with Task and Task, should someone veer off the desired path of just awaiting them. In general, the following operations should never be performed on a ValueTask / ValueTask:

  • Awaiting a ValueTask / ValueTask multiple times. The underlying object may have been recycled already and be in use by another operation. In contrast, a Task / Task will never transition from a complete to incomplete state, so you can await it as many times as you need to, and will always get the same answer every time.
  • Awaiting a ValueTask / ValueTask concurrently. The underlying object expects to work with only a single callback from a single consumer at a time, and attempting to await it concurrently could easily introduce race conditions and subtle program errors. It’s also just a more specific case of the above bad operation: “awaiting a ValueTask / ValueTask multiple times.” In contrast, Task / Task do support any number of concurrent awaits.
  • Using .GetAwaiter().GetResult() when the operation hasn’t yet completed. The IValueTaskSource / IValueTaskSource implementation need not support blocking until the operation completes, and likely doesn’t, so such an operation is inherently a race condition and is unlikely to behave the way the caller intends. In contrast, Task / Task do enable this, blocking the caller until the task completes.

If you have a ValueTask or a ValueTask and you need to do one of these things, you should use .AsTask() to get a Task / Task and then operate on that resulting task object. After that point, you should never interact with that ValueTask / ValueTask again.

The short rule is this: with a ValueTask or a ValueTask, you should either await it directly (optionally with .ConfigureAwait(false)) or call AsTask() on it directly, and then never use it again, e.g.

and from the MSDN documentation on ValueTask and ValueTask<T>:

The following operations should never be performed on a ValueTask instance:

  • Awaiting the instance multiple times.
  • Calling AsTask multiple times.
  • Using .Result or .GetAwaiter().GetResult() when the operation hasn't yet completed, or using them multiple times.
  • Using more than one of these techniques to consume the instance.

If you do any of the above, the results are undefined.

You can read more about ValueTask and ValueTask<T> here and here.

Upvotes: 8

Manuel Schweigert
Manuel Schweigert

Reputation: 4974

You can create a completed task using Task.FromResult(value) and await it:

var result = await Task.FromResult(5);
Debug.Assert(result == 5);

This is useful for example if you have a method which can return cached data but needs to fetch it asynchronously the first time.

So, yes, you can await already completed tasks.

Upvotes: 5

David Pine
David Pine

Reputation: 24525

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.

Upvotes: 27

Vincent
Vincent

Reputation: 3746

You will have immédiat the result. Once the task is complete, its result property will contain the result and keep it.

Upvotes: 1

Related Questions