ValueTask.Preserve()有什么作用?

4

ValueTaskValueTask<TResult>都有一个Preserve()方法,其功能概述为“获取一个可用于任何未来时间点的ValueTask。”

这是什么意思,我们应该在什么情况下使用它?这是否意味着一个“普通”的ValueTask不能在“任何未来时间点”使用?如果是这样,为什么?

2个回答

5

ValueTask 是对 Task 的性能优化,但这种性能优化是有代价的:您不能像自由使用 Task 一样自由地使用 ValueTask文档 中提到了以下限制:

不应在 ValueTask 实例上执行以下操作:

  • 多次等待该实例。
  • 多次调用 AsTask
  • 同时使用多个技术来消耗该实例。
这些限制仅适用于由 IValueTaskSource 支持的 ValueTask。一个 ValueTask 也可以由一个 TaskTResult 值(对于 ValueTask<TResult>)支持。如果您有 100% 的把握知道一个 ValueTask 不是由 IValueTaskSource 支持的,那么您可以像使用 Task 一样自由地使用它。例如,您可以多次使用 await,或者同步等待其完成并使用 .GetAwaiter().GetResult()
一般情况下,你不知道 ValueTask 的内部实现方式,即使你通过研究源代码了解了它的实现方式,你也可能不想依赖于实现细节。使用 ValueTask.Preserve 方法,你可以创建一个新的 ValueTask,该任务表示原始的 ValueTask,并且可以在没有限制的情况下使用,因为它没有被 IValueTaskSource 支持。如果原始的 ValueTask 没有被 IValueTaskSource 支持,则此方法仅影响原始任务(它只是返回未更改的原始任务)。
ValueTask preserved = originalValueTask.Preserve();

调用Preserve方法后,原始的ValueTask已被使用。不应再等待它,也不要再次调用Preserve或使用AsTask方法转换为Task。这些操作中的任何一个都可能导致InvalidOperationException异常。但现在你有了preserved表示形式,可以像Task一样自由地使用它。

我的答案最初包含了一个使用Preserve方法的实际示例,但后来证明是不正确的。这个示例可以在该答案的第4版中找到。


如果我理解正确:如果我不立即等待ValueTask并将其保存到变量中,但我还确保除了恰好一次等待它之外,我不会对它做任何事情,那么我就不必使用Preserve()。另一方面,如果我需要多次安全地等待/AsTask(),则应调用Preserve()以确保我始终可以这样做。 - Petrusion
1
@Petrusion 是的,完全正确。通常情况下,您不需要使用 Preserve,因为您只需等待异步方法而无需将 ValueTask 存储在变量中。 - Theodor Zoulias

2

这份文档不是很清晰。从源代码中可以看到:

/// <summary>null if representing a successful synchronous completion, 
/// otherwise a <see cref="Task"/> or a <see cref="IValueTaskSource"/>.</summary>
internal readonly object? _obj;

ValueTask 在内部声明了一个可空容器对象,用于保存 ValueTask 封装的 TaskIValueTaskSource,如果它是一个封装了这些内容的 ValueTask 的话。否则,如果 ValueTask 代表一个完成,则为 null

public ValueTask Preserve() => _obj == null ? this : new ValueTask(AsTask());

Preserve() 方法返回一个新的 ValueTask,用于表示其底层任务,如果 ValueTask 表示一个 Task 或者一个 IValueTaskSource(即: _obj != null),否则,如果 ValueTask 表示同步完成,则返回自身。这是必要的,因为 _obj 是内部的,无法在外部进行测试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接