ValueTask
和ValueTask<TResult>
都有一个Preserve()
方法,其功能概述为“获取一个可用于任何未来时间点的ValueTask。”
这是什么意思,我们应该在什么情况下使用它?这是否意味着一个“普通”的ValueTask
不能在“任何未来时间点”使用?如果是这样,为什么?
ValueTask
和ValueTask<TResult>
都有一个Preserve()
方法,其功能概述为“获取一个可用于任何未来时间点的ValueTask。”
这是什么意思,我们应该在什么情况下使用它?这是否意味着一个“普通”的ValueTask
不能在“任何未来时间点”使用?如果是这样,为什么?
ValueTask
是对 Task
的性能优化,但这种性能优化是有代价的:您不能像自由使用 Task
一样自由地使用 ValueTask
。文档 中提到了以下限制:
这些限制仅适用于由不应在
ValueTask
实例上执行以下操作:
- 多次等待该实例。
- 多次调用
AsTask
。- 同时使用多个技术来消耗该实例。
IValueTaskSource
支持的 ValueTask
。一个 ValueTask
也可以由一个 Task
或 TResult
值(对于 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版中找到。
这份文档不是很清晰。从源代码中可以看到:
/// <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
封装的 Task
或 IValueTaskSource
,如果它是一个封装了这些内容的 ValueTask
的话。否则,如果 ValueTask
代表一个完成,则为 null
。
public ValueTask Preserve() => _obj == null ? this : new ValueTask(AsTask());
Preserve()
方法返回一个新的 ValueTask
,用于表示其底层任务,如果 ValueTask
表示一个 Task
或者一个 IValueTaskSource
(即: _obj != null
),否则,如果 ValueTask
表示同步完成,则返回自身。这是必要的,因为 _obj
是内部的,无法在外部进行测试。
Preserve
,因为您只需等待异步方法而无需将ValueTask
存储在变量中。 - Theodor Zoulias