Search code examples
c#asynchronousvaluetask

Do you have to await a ValueTask?


The title says it all:

The specs explicitly say that you must never await a ValueTask more than once - because in particular in the truly asynchronous case, the backing state machine may be pooled and recycled for other ValueTasks after awaiting it.

But what about awaiting less than once - i.e., never? Could this cause any issues? For example, would the backing state still be able to be returned to the pool even if nobody awaited the result?

async Task IsThisLegal()
{
  var _ = SomeValueTask();
  await Task.Delay(10);
  // The value task would have completed by now, but we're not awaiting it. Is that ok?
}

async ValueTask<bool> SomeValueTask()
{
  await Task.Delay(TimeSpan.FromSeconds(1));
  return true;
}

Solution

  • Do you have to await a ValueTask?

    No. Not nessesarily.

    Never awaiting. Could this cause any issues?

    Yes, potentially.

    1. General issues with fire-and-forget, like not knowing if an exception occurred, and not knowing if the operation completed at all. For example in an ASP.NET application the worker process might be recycled at any moment, terminating abruptly any pending operations that have been launched in a fire-and-forget fashion.
    2. Issues with concurrency. Many components are equipped with asynchronous APIs, but they do not support concurrency. If you start an asynchronous operation before the previous operation has completed, the component has undefined behavior (usually throws an exception). An example of such a component is the Stream class, equipped with ReadAsync and WriteAsync APIs. By not awaiting the ValueTask, you can't know when it's safe to proceed with the next operation on the same instance of the component.
    3. Issues with memory efficiency. APIs that return ValueTasks are frequently implemented with an IValueTaskSource backing state that is pooled and reused, minimized the allocations and reducing the pressure on the garbage collector. By not awaiting the ValueTask, the component has no way to know that the ValueTask has been consumed, so it can't reuse the backing state. As a result it will create a new backing state for the next operation. There is no formal way to tell the component that "I promise to never await this ValueTask". All IValueTaskSource implementations that I've seen use the GetResult method as an indicator that the ValueTask has been consumed. The GetResult is called automatically by the .NET infrastructure, whenever a ValueTask is awaited.