5

The question asked here is the same as the one here and is aimed to create a definitive solution to it. The most accurate answer is by Stephen Toub himself in this issue that is exactly about this question. The "recommendended code" is the following one:

public static ValueTask AsValueTask<T>(this ValueTask<T> valueTask)
{
    if (valueTask.IsCompletedSuccessfully)
    {
        valueTask.GetResult();
        return default;
    }

    return new ValueTask(valueTask.AsTask());
}

This answer is not up-to-date - a ValueTask doesn't expose a GetResult() (only a Result property) - and THE question is:

  • Do we need to "pull" the Result out of the ValueTask (to "release" the IValueTaskSource that may operate under this ValueTask)?
  • If yes:
    • is it the .GetAwaiter() call that is missing above?
    • OR is a fake call to the property is guaranteed to work var fake = valueTask.Result;? Always? (I'm afraid of dead code elimination.)
  • If not, is a straight implementation like the following one enough (and optimal)?
public static ValueTask AsNonGenericValueTask<T>( in this ValueTask<T> valueTask )
{
    return valueTask.IsCompletedSuccessfully ? default : new ValueTask( valueTask.AsTask() );
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Spi
  • 686
  • 3
  • 18

2 Answers2

3

What's missing in that code is .GetAwaiter():

public static ValueTask AsValueTask<T>(this ValueTask<T> valueTask)
{
    if (valueTask.IsCompletedSuccessfully)
    {
        valueTask.GetAwaiter().GetResult();
        return default;
    }

    return new ValueTask(valueTask.AsTask());
}

You're partially right in that you don't care about the result. But you might care about a thrown exception or cancellation that you'll miss if you don't query the result.

Or you can write it like this:

public static async ValueTask AsValueTask<T>(this ValueTask<T> valueTask)
    => await valueTask;
Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59
0

You can use either:

valueTask.GetAwaiter().GetResult();

...or:

_ = valueTask.Result;

Both of those delegate to the underlying IValueTaskSource<T>.GetResult method, assuming that the ValueTask<T> is backed by a IValueTaskSource<T>. Using the shorter (second) approach should be slightly more efficient, since it involves one less method invocation.

You could also omit getting the result entirely. There is no requirement that a ValueTask<T> must be awaited at least once, or that its result must be retrieved at least once. It is entirely valid to be handed a ValueTask<T> and then forget about it. The documented restriction is:

A ValueTask<TResult> instance may only be awaited once, [...]

It's a "may only", not a "must".

It is still a good idea to get the result though. By retrieving the result you are signaling that the ValueTask<TResult> has been consumed, and so the underlying IValueTaskSource<T> can be reused. Reusing IValueTaskSource<T> instances makes ValueTask<T>-based implementations more efficient, because they allocate memory less frequently. To get an idea, take a look at the internal System.Threading.Channels.AsyncOperation<TResult> class. This class implements the IValueTaskSource<TResult> interface. Here is how the GetResult is implemented:

/// <summary>Gets the result of the operation.</summary>
/// <param name="token">The token that must match <see cref="_currentId"/>.</param>
public TResult GetResult(short token)
{
    if (_currentId != token)
    {
        ThrowIncorrectCurrentIdException();
    }

    if (!IsCompleted)
    {
        ThrowIncompleteOperationException();
    }

    ExceptionDispatchInfo? error = _error;
    TResult? result = _result;
    _currentId++;

    if (_pooled)
    {
        Volatile.Write(ref _continuation, s_availableSentinel); // only after fetching all needed data
    }

    error?.Throw();
    return result!;
}

Setting the private field Action<object> _continuation to the static readonly value s_availableSentinel allows the AsyncOperation<TResult> to be reused by a subsequent asynchronous operation (like the ChannelReader.ReadAsync for example). Otherwise the next asynchronous operation will allocate a new AsyncOperation<TResult> instance.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104