从 C# 7.0 开始,异步方法可以返回 ValueTask。解释说当我们有缓存结果或通过同步代码模拟异步时应该使用它。但是,我仍然不明白始终使用 ValueTask 有什么问题,或者实际上为什么 async/await 不是从一开始就使用值类型构建的。ValueTask 什么时候不能完成这项工作?
从API 文档(强调添加):
当方法的操作结果很可能同步可用 并且 期望方法被如此频繁地调用以致Task<TResult>为每次调用分配新的成本将过高时,方法可能会返回此值类型的实例。 使用 aValueTask<TResult>而不是 a需要权衡取舍Task<TResult>。例如,虽然 aValueTask<TResult>可以帮助避免在成功结果同步可用的情况下进行分配,但它也包含两个字段,而Task<TResult>作为引用类型的 a 是单个字段。这意味着方法调用最终会返回两个值得数据的字段,而不是一个需要复制的数据。这也意味着,如果在方法中等待返回其中之一的async方法,则该方法的状态机async将更大,因为需要存储两个字段而不是单个引用的结构。 此外,对于通过使用异步操作的结果以外的用途await,ValueTask<TResult>可能会导致更复杂的编程模型,这反过来实际上会导致更多的分配。例如,考虑一个方法,它可以返回Task<TResult>带有缓存任务的 a 作为公共结果,也可以返回ValueTask<TResult>. 如果结果的使用者想要将其用作 a Task<TResult>,例如在Task.WhenAlland之类的方法中使用 with Task.WhenAny,则ValueTask<TResult>首先需要将 the 转换为Task<TResult>using AsTask,这会导致如果使用了缓存Task<TResult>则可以避免的分配首先。 因此, 任何异步方法的默认选择应该是返回 aTask或Task<TResult>。只有当性能分析证明值得使用时,才应该ValueTask<TResult>使用 a 而不是Task<TResult>.
当方法的操作结果很可能同步可用 并且 期望方法被如此频繁地调用以致Task<TResult>为每次调用分配新的成本将过高时,方法可能会返回此值类型的实例。
Task<TResult>
使用 aValueTask<TResult>而不是 a需要权衡取舍Task<TResult>。例如,虽然 aValueTask<TResult>可以帮助避免在成功结果同步可用的情况下进行分配,但它也包含两个字段,而Task<TResult>作为引用类型的 a 是单个字段。这意味着方法调用最终会返回两个值得数据的字段,而不是一个需要复制的数据。这也意味着,如果在方法中等待返回其中之一的async方法,则该方法的状态机async将更大,因为需要存储两个字段而不是单个引用的结构。
ValueTask<TResult>
async
此外,对于通过使用异步操作的结果以外的用途await,ValueTask<TResult>可能会导致更复杂的编程模型,这反过来实际上会导致更多的分配。例如,考虑一个方法,它可以返回Task<TResult>带有缓存任务的 a 作为公共结果,也可以返回ValueTask<TResult>. 如果结果的使用者想要将其用作 a Task<TResult>,例如在Task.WhenAlland之类的方法中使用 with Task.WhenAny,则ValueTask<TResult>首先需要将 the 转换为Task<TResult>using AsTask,这会导致如果使用了缓存Task<TResult>则可以避免的分配首先。
await
Task.WhenAll
Task.WhenAny
AsTask
因此, 任何异步方法的默认选择应该是返回 aTask或Task<TResult>。只有当性能分析证明值得使用时,才应该ValueTask<TResult>使用 a 而不是Task<TResult>.
Task