Sti*_*gar 146 c# asynchronous
从C#7.0开始,异步方法可以返回ValueTask <T>.解释说,当我们有缓存结果或通过同步代码模拟异步时,应该使用它.但是我仍然不明白使用ValueTask的问题是什么,或者实际上为什么async/await不是从一开始就使用值类型构建的.ValueTask何时无法完成这项工作?
Ste*_*ary 218
从API文档(重点添加):
方法可能会返回此值类型的实例,因为它们的操作结果可能是同步可用的,并且当预期方法被频繁调用时,
Task<TResult>
为每个调用分配新值的成本将会过高.使用a
ValueTask<TResult>
而不是a 进行权衡Task<TResult>
.例如,虽然aValueTask<TResult>
可以帮助避免在成功结果同步可用的情况下进行分配,但它也包含两个字段,而aTask<TResult>
作为引用类型是单个字段.这意味着方法调用最终会返回两个字段而不是一个字段,这是要复制的更多数据.这也意味着如果在方法中等待返回其中一个的async
方法,则async
由于需要存储两个字段而不是单个引用的结构,该方法的状态机将更大.此外,对于除了消耗异步操作的结果之外的用途
await
,ValueTask<TResult>
可以导致更复杂的编程模型,这反过来可以实际上导致更多的分配.例如,考虑一种方法,该方法可以返回Task<TResult>
带有缓存任务的a作为常见结果或者ValueTask<TResult>
.如果结果的消费者想用它作为Task<TResult>
,比如在类似的方法使用Task.WhenAll
和Task.WhenAny
,则ValueTask<TResult>
首先需要转换成Task<TResult>
使用AsTask
,这导致如果高速缓存会被避免了分配Task<TResult>
已经使用首先.因此,任何异步方法的默认选择应该是返回一个
Task
或Task<TResult>
.只有在性能分析证明它值得的时候才应该ValueTask<TResult>
使用而不是Task<TResult>
.
Eri*_*ert 96
但是我仍然不明白使用ValueTask的问题是什么
结构类型不是免费的.复制大于引用大小的结构可能比复制引用要慢.存储大于引用的结构比存储引用需要更多的内存.当可以注册引用时,可能不会注册大于64位的结构.降低收集压力的好处可能不会超过成本.
应该通过工程学科来解决性能问题.制定目标,根据目标衡量进度,然后决定如果不满足目标,如何修改程序,一路测量以确保您的更改实际上是改进.
为什么async/await不是从一开始就使用值类型构建的.
await
在Task<T>
类型已经存在之后很久就被添加到C#中了.如果已经存在新类型,那么发明新类型会有些不正常.并且await
经过了大量的设计迭代,然后才决定2012年出货的那个.完美是善的敌人; 更好地提供适用于现有基础架构的解决方案,然后如果有用户需求,请在以后提供改进.
我还注意到,允许用户提供的类型作为编译器生成方法的输出的新功能增加了相当大的风险和测试负担.当您可以返回的唯一内容无效或任务时,测试团队不必考虑返回某些绝对疯狂类型的任何情况.测试编译器装置,找出不只是什么节目的人很可能会写,但什么程序是可以写的,因为我们希望编译器来编译所有的法律的程序,不可能每个人都理智的程序.这太贵了.
有人可以解释ValueTask何时无法完成这项工作?
事物的目的是提高性能.它不会做的工作,如果它不可测量和显著提高性能.它无法保证.
15e*_*153 23
ValueTask<T>
它不是一个子集Task<T>
,它是一个超集.
ValueTask<T>
是T和a的区别联合Task<T>
,使其无分配ReadAsync<T>
以同步返回它可用的T值(与使用相比Task.FromResult<T>
,需要分配一个Task<T>
实例).ValueTask<T>
是等待的,所以大多数实例的消费都与a无法区分Task<T>
.作为结构体的ValueTask允许编写异步方法,这些异步方法在同步运行时不分配内存而不会影响API一致性.想象一下,有一个Task返回方法的接口.实现此接口的每个类必须返回一个Task,即使它们恰好同步执行(希望使用Task.FromResult).您当然可以在接口上使用两种不同的方法,一种是同步方法,另一种是异步方法,但这需要2种不同的实现来避免"同步异步"和"异步过同步".
因此,它允许您编写一个异步或同步的方法,而不是为每个方法编写一个相同的方法.您可以在任何地方使用它,Task<T>
但通常不会添加任何内容.
好吧,它增加了一件事:它向调用者添加了一个隐含的承诺,即该方法实际上使用了ValueTask<T>
提供的附加功能.我个人更喜欢选择尽可能多地告诉来电者的参数和返回类型.IList<T>
如果枚举不能提供计数,请不要返回; IEnumerable<T>
如果可以,不要回来.您的消费者不应该查找任何文档,以了解哪些方法可以合理地同步调用,哪些不可以.
我不认为未来的设计变化是一个有说服力的论点.恰恰相反:如果一个方法改变了它的语义,它应该破坏构建,直到相应地更新对它的所有调用.如果这被认为是不合需要的(并且相信我,我同情不打破构建的愿望),请考虑接口版本控制.
这基本上就是强类型的用途.
如果某些程序员在您的商店中设计异步方法无法做出明智的决策,那么为每个经验不足的程序员分配一名高级导师并进行每周代码审查可能会有所帮助.如果他们猜错了,请解释为什么应该采用不同的方式.对于资深球员来说,这是一个开销,但它会使得三年级球员的速度提升得更快,而不仅仅是让他们在深层投球并给予他们一些随意的规则.
如果编写该方法的人不知道它是否可以同步调用,那么地球上的人呢?!
如果你有那么多没有经验的程序员编写异步方法,那些同样的人也会调用它们吗?他们是否有资格自己找出哪些可以安全地称为异步,或者他们是否会开始对他们如何称呼这些东西应用类似的任意规则?
这里的问题不是你的返回类型,它是程序员被置于他们尚未准备好的角色.这一定是因为某种原因而发生的,所以我确信修复它不是一件容易的事.描述它当然不是一个解决方案.但是寻找一种方法将问题隐藏在编译器之外也不是解决方案.
uns*_*Ptr 16
.Net Core 2.1有一些变化.从.net core 2.1开始,ValueTask不仅可以表示同步已完成的操作,还可以表示async已完成.另外我们收到非泛型ValueTask
类型.
我将留下与您的问题相关的Stephen Toub 评论:
我们仍然需要正式化指导,但我希望它对于公共API表面区域将是这样的:
任务提供最大的可用性.
ValueTask为性能优化提供了最多选项.
- 如果您正在编写其他人将覆盖的接口/虚拟方法,则ValueTask是正确的默认选择.
- 如果您希望API在分配很重要的热路径上使用,那么ValueTask是一个不错的选择.
- 否则,在性能不重要的情况下,默认为Task,因为它提供了更好的保证和可用性.
从实现的角度来看,许多返回的ValueTask实例仍将由Task支持.
功能不仅可以在.net核心2.1中使用.您将能够将它与System.Threading.Tasks.Extensions包一起使用.
Shu*_*han 11
来自 Marc 的最新信息(2019 年 8 月)
当某件事通常或总是真正异步时(即不立即完成),请使用任务;当某些事情通常或总是同步时使用 ValueTask,即值将是内联已知的;还可以在无法知道答案的多态场景(虚拟、接口)中使用 ValueTask。
来源:https ://blog.marcgravell.com/2019/08/prefer-valuetask-to-task-always-and.html
当我有类似的问题时,我按照上面的博客文章进行了最近的项目。
截至 2019 年 8 月 23 日,来自Marc Gravell 的更新#2 (来自他的博客):
因此,回到之前何时使用 Task 与 ValueTask 的问题,IMO 的答案现在很明显:
使用 ValueTask[],除非你绝对不能,因为现有的 API 是 Task[],即使这样:至少考虑一下 API 中断。
另请记住:仅等待任何单个可等待表达式一次
如果我们将这两件事放在一起,图书馆和 BCL 就可以在后台自由地创造奇迹,以提高性能,而无需调用者关心。
归档时间: |
|
查看次数: |
23797 次 |
最近记录: |