将 ValueTask<T> 转换为非通用 ValueTask

Convert a ValueTask<T> to a non generic ValueTask

这里提出的问题与 相同,旨在为其创建一个明确的解决方案。 最准确的答案是 Stephen Toub 自己在 this issue 中给出的,这正是关于这个问题的。 "recommendended code" 是以下一个:

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

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

此答案不是最新的 - ValueTask 不公开 GetResult()(仅公开结果 属性) - 问题是:

public static ValueTask AsNonGenericValueTask<T>( in this ValueTask<T> valueTask )
{
    return valueTask.IsCompletedSuccessfully ? default : new ValueTask( valueTask.AsTask() );
}

该代码中缺少的是 .GetAwaiter():

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

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

你说对了一部分,因为你不关心结果。但是您可能会关心抛出的异常或取消,如果您不查询结果,您会错过这些。

或者你可以这样写:

public static async ValueTask AsValueTask<T>(this ValueTask<T> valueTask)
    => await valueTask;

您可以使用:

valueTask.GetAwaiter().GetResult();

...或者:

_ = valueTask.Result;

两者都委托给基础 IValueTaskSource<T>.GetResult method, assuming that the ValueTask<T> is backed by a IValueTaskSource<T>。使用更短的(第二种)方法应该稍微更有效,因为它涉及更少的方法调用。

您也可以完全忽略获取结果。没有要求必须至少等待一次 ValueTask<T>,或者必须至少检索一次其结果。收到 ValueTask<T> 然后忘记它是完全有效的。 documented restriction 是:

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

这是“只能”,而不是“必须”

虽然得到结果仍然是个好主意。通过检索结果,您表示 ValueTask<TResult> 已被消耗,因此可以重用基础 IValueTaskSource<T>。重用 IValueTaskSource<T> 个实例使基于 ValueTask<T> 的实现更加高效,因为它们分配内存的频率较低。要获得一个想法,请查看内部 System.Threading.Channels.AsyncOperation<TResult> class. This class implements the IValueTaskSource<TResult> interface. Here is how the GetResult 的实现:

/// <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!;
}

将私有字段 Action<object> _continuation 设置为静态只读值 s_availableSentinel 允许 AsyncOperation<TResult> 被后续异步操作重用(例如 ChannelReader.ReadAsync) .否则下一个异步操作将分配一个新的 AsyncOperation<TResult> 实例。