【问题标题】:Convert a ValueTask<T> to a non generic ValueTask将 ValueTask<T> 转换为非通用 ValueTask
【发布时间】:2020-04-16 17:55:14
【问题描述】:

这里提出的问题与one here 相同,旨在为它创建一个明确的解决方案。 最准确的答案是 Stephen Toub 本人在this issue 中提出的,这正是这个问题。 “推荐代码”如下:

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

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

这个答案不是最新的——ValueTask 没有公开 GetResult()(只有 Result 属性)——问题是:

  • 我们是否需要从 ValueTask 中“拉”出 Result(以“释放”可能在此 ValueTask 下运行的 IValueTaskSource)?
  • 如果是:
    • 是上面缺少的.GetAwaiter() 调用吗?
    • 或者是一个假电话保证工作var fake = valueTask.Result;属性?总是? (我害怕死代码消除。)
  • 如果不是,像下面这样的直接实现是否足够(并且是最佳的)?
public static ValueTask AsNonGenericValueTask<T>( in this ValueTask<T> valueTask )
{
    return valueTask.IsCompletedSuccessfully ? default : new ValueTask( valueTask.AsTask() );
}

【问题讨论】:

    标签: c# .net async-await valuetask


    【解决方案1】:

    该代码中缺少的是.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;
    

    【讨论】:

    • 虽然提到的代码段在功能上相似,但 ILSPY 之旅显示了非常不同的实现。 top 方法生成的代码要少得多。 (至少在 .net 5.0 中)
    • 当然可以。第二种方法会生成状态机,第一种不会。
    • “你可能关心抛出的异常或取消,如果你不查询结果,你会错过。” -- 不在 if 块内已通过IsCompletedSuccessfully 测试!
    【解决方案2】:

    你可以使用:

    valueTask.GetAwaiter().GetResult();
    

    ...或:

    _ = valueTask.Result;
    

    这两个都委托给底层的IValueTaskSource&lt;T&gt;.GetResult 方法,假设ValueTask&lt;T&gt;IValueTaskSource&lt;T&gt; 支持。使用更短的(第二种)方法应该会更有效一些,因为它涉及的方法调用更少。

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

    ValueTask&lt;TResult&gt; 实例只能等待一次,[...]

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

    尽管如此,获得结果仍然是一个好主意。通过检索结果,您表示ValueTask&lt;TResult&gt; 已被使用,因此可以重用底层IValueTaskSource&lt;T&gt;。重用IValueTaskSource&lt;T&gt; 实例使基于ValueTask&lt;T&gt; 的实现更加高效,因为它们分配内存的频率较低。要了解一个想法,请查看内部 System.Threading.Channels.AsyncOperation&lt;TResult&gt; 类。此类实现IValueTaskSource&lt;TResult&gt; 接口。以下是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&lt;object&gt; _continuation 设置为静态只读值s_availableSentinel 允许后续异步操作重用AsyncOperation&lt;TResult&gt;(例如ChannelReader.ReadAsync)。否则下一个异步操作将分配一个新的AsyncOperation&lt;TResult&gt; 实例。

    【讨论】:

      猜你喜欢
      • 2020-04-07
      • 2017-06-14
      • 2019-02-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-17
      • 2018-08-02
      • 1970-01-01
      相关资源
      最近更新 更多