ValueTask.Preserve() 的意义何在?

What is the point of ValueTask.Preserve()?

ValueTaskValueTask<TResult> 有一个 Preserve() 方法,总结为“获取一个 ValueTask,可以在将来的任何时候使用。”

这是什么意思,我们应该在什么时候使用它?这是否意味着 'normal' ValueTask 不能“在未来的任何时候”使用?如果是,为什么?

文档不是很清楚。来自 the source :

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

ValueTask 在内部声明了一个可为 null 的容器对象来保存 ValueTask 封装的 TaskIValueTaskSource 应该是 ValueTask所以。如果 ValueTask 表示完成,则为 null

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

Preserve() returns 一个新的 ValueTask 表示其基础任务,在 ValueTask 表示 TaskIValueTaskSource 的情况下(即:_obj != null)或者如果 ValueTask 表示成功的同步完成,则只是 returns 本身。这是必要的,因为 _obj 是内部的,无法在外部进行测试。

ValueTask is a performance optimization over a Tasks, but this performance comes with a cost: You cannot use a ValueTask as freely as a Task. The documentation 提到了这些限制:

The following operations should never be performed on a ValueTask instance:

  • Awaiting the instance multiple times.
  • Calling AsTask multiple times.
  • Using more than one of these techniques to consume the instance.

这些限制仅适用于由 IValueTaskSource 支持的 ValueTaskValueTask 也可以由 TaskTResult 值支持(对于 ValueTask<TResult>)。如果您 100% 确定 ValueTask 不受 IValueTaskSource 支持,您可以像 Task 一样自由使用它。例如你可以 await 多次,或者你可以用 .GetAwaiter().GetResult().

同步等待它完成

通常您不知道 ValueTask 是如何在内部实现的,即使您知道(通过研究源代码)您也可能不想依赖实现细节。使用 ValueTask.Preserve 方法,您可以创建一个新的 ValueTask 来表示原始 ValueTask,并且可以不受限制地使用,因为它不是由 IValueTaskSource 烘焙的。此方法仅在 IValueTaskSource 支持时才影响原始 ValueTask,否则它是 no-op(它只是 returns 未更改的原始任务)。

ValueTask preserved = originalValueTask.Preserve();

调用Preserve后,原来的ValueTask已经消耗掉了。它不应再等待,或再次 Preserved,或使用 AsTask 方法转换为 Task。执行任何这些操作都可能导致 InvalidOperationException。但是现在你有了它的 preserved 表示,它可以像 Task.

一样自由使用

我的回答最初包括一个使用Preserve方法的实际例子,结果证明是不正确的。这个例子可以在这个答案的 4th revision 中找到。