Home > Software design >  Do I need to query the .Result property of a ValueTask that I choose to not await?
Do I need to query the .Result property of a ValueTask that I choose to not await?

Time:05-05

Let's say I have a method that returns an object of type ValueTask, for example the WaitToReadAsync method on ChannelReader. There are three ways I can consume this ValueTask. I can either:

  1. Directly await it.
  2. Turn it into a Task, then await it (with .AsTask())
  3. Query the .Result property if it has completed synchronously (if .IsCompleted is true).

With regards to the third method (calling .Result on the ValueTask), the MSDN documentation states that this property can only be queried once. But, does this property need to be queried at all? In other words, is it okay to never query the result of a ValueTask?

Here is an example of what I mean:

var channel = Channel.CreateUnbounded<int>();
var waitToReadValueTask = channel.Reader.WaitToReadAsync(default);
if (waitToReadValueTask.IsCompleted)
{
   if (channel.Reader.TryRead(out var result))
   {
      // Do something with the result object.
   }
}

In this example, the waitToReadValueTask was never consumed. It was never awaited, neither was the .Result property called on it. Is this okay?

CodePudding user response:

Good question. Yes, it is perfectly OK to omit querying the Result property of a ValueTask<T>, provided that you are not interested about the result, or about the Exception that might be stored inside the task. It is also OK to ignore entirely a ValueTask after its creation, if you so wish, resulting in a so-called fire-and-forget task. The task will still complete, even if you haven't kept any reference of it.

In general it is a good idea to consume your ValueTasks though, either by awaiting them or by AsTasking them or by querying their Result (provided that they are already completed). By consuming a ValueTask you signal to the underlying implementation that the backing IValueTaskSource<T> instance can be reused. Reusing IValueTaskSource<T> instances is how the ValueTask-based APIs are avoiding memory allocations, when the asynchronous method cannot complete synchronously.

In your example you are querying the IsCompleted property immediately after creating the ValueTask, so it is highly unlikely that the ValueTask was not completed upon creation. So regarding your example the above advice has only theoretical value. ValueTasks that are completed upon creation are not backed by IValueTaskSource<T> instances, so there is no performance implication by not querying the Result in your example. The advice would be of practical value if you queried the IsCompleted property at a later time after creating the ValueTask.

  • Related