欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【5min+】 秋名山的竞速。 ValueTask 和 Task

程序员文章站 2023-09-29 08:58:49
伴随着dotnet core的不断迭代,我们在享受.net性能上的提升之外,还收获了许许多多新出现的API。不知您有没有发现,有这样一个类型在开始逐渐出现在我们的视野中 ———— ValueTask ......

系列介绍

简介

【五分钟的dotnet】是一个利用您的碎片化时间来学习和丰富.net知识的博文系列。它所包含了.net体系中可能会涉及到的方方面面,比如c#的小细节,aspnetcore,微服务中的.net知识等等。

场景

您可以在下班坐地铁的时候,拿出手机逛一逛博客园,利用短短的五分钟完成阅读。

诞生缘由

  • 曾经学过的内容可能过不了多久就忘了,我们需要一些文章来帮我们查漏补缺。
  • 太长篇幅的文章看着滚动条就害怕了,我们可能更期望文字少的文章。
  • .net体系的内容太多了,平时也不知道该学哪些,我们可能需要一点点知识线索。

文章质量

当然,并不意味着它篇幅短就质量差。所谓麻雀虽小五脏俱全,我们会尽可能保证利用最少的文字去详细的阐述内容。

正文

伴随着dotnet core的不断迭代,我们在享受.net性能上的提升之外,还收获了许许多多新出现的api。不知您有没有发现,有这样一个类型在开始逐渐出现在我们的视野中 ———— valuetask

比如在最新的ef core中:

public virtual async valuetask<entityentry> addasync(
    object entity,
    cancellationtoken cancellationtoken = default)

以上代码是ef core中dbcontext的addasync签名,我们可以发现它的返回类型为valuetask,可能就如同您想的一样,既然addasync是这样,那异步查找方法返回的类型也是这样。是的,曾经这些由task来包裹的结果,现在全部交由vauletask来处理了。

在最新的c# 8的特性中,引入了 的概念。它在原有的同步迭代器的基础上,扩充了异步的迭代器版本:

iasyncenumerableiasyncenumerator

如果您还不了解同步的迭代器接口,可以查看本系列的 。

而这个异步迭代器接口的方法签名是这样的:

public interface iasyncenumerator<out t> : iasyncdisposable
{
    t current { get; }
    valuetask<bool> movenextasync();
}

omg,又是valuetask!!!

那么,valuetask到底是什么东西呢?它和传统的task又有什么区别呢?该在什么时候使用它。

不要慌,接下来的五分钟您将get到它。

开胃菜

在开始之前,我们先来了解一下咱们.net中对内存中对象的存储格式:堆与栈。

先来看栈和堆的区别:

  • 栈,或多或少负责跟踪正在程序中运行的代码。栈空间比较小,但是读取速度快
  • 堆,或多或少负责跟踪程序对象或数据。堆空间比较大,但是读取速度慢

而在c#里面(其它.net语言同理哈),咱们都知道有class 和 struct这两个类别,这两个类别对应的就是引用类型和值类型。

我们先拿实例化一个类来说,比如我们在执行 var newinstance = new classa()的时候,我们就会建立一个a的对象,而这个对象的数据一般来说就是分配在堆上的,而同时会建立一个引用id,该id就一般就置放在栈上面。

那么值类型的数据呢?一般来说它是存放在栈上的。当然这句话不全对:

"值类型存储在栈中, 引用类型存储在堆中” 这句话的前半句是有争议的,“变量的值是在它声明的位置存储的,假如一个类中有一个int类型的实例变量,那么在这个类的任何对象中,该变量的值总是和对象中的其他数据在一起,也就是在堆上,只有局部变量(方法内部声明的变量)和方法参数在栈上。而对于c#2以及更高版本,很多局部变量并不完全存放在栈中”引用-《c# in depth》及译本《深入理解c#》.

这也是为什么我们会将结构化的小数据创建为struct的原因,比如具有(r,g,b)三个属性的结构color。

栈里面的数据一般来说因为空间小,读取数据库的原因,它的生命周期就比较小,比如一个返回值为int的方法,当方法完成之后,该栈中的数据就销毁了。而堆呢?堆保存了几乎所有类中的数据,它怎么销毁数据来保存内存不溢出呢? 是的,您会想到gc,在.net中就是一个专门的垃圾回收器来完成该操作。

开始飙车

回到本篇文章的主题,valuetask。 task可能大家都用的比较多了,毕竟从dotnet framework的年代就流传至今,而valuetask却从dotnet core2.0才引入。

我们先来看看 msdn 中对valuetask的阐述:

提供异步操作的可等待结果。提供包装 task 和 tresult(仅使用其中之一)的值类型。

往下滑msdn,就能看到里面有一个很重要的一点:

there are tradeoffs to using a valuetask instead of a task. for example, while a valuetask can help avoid an allocation in the case where the successful result is available synchronously, it also contains multiple fields, whereas a task as a reference type is a single field.

不要问为什么这个是英文,因为我尝试msdn的机翻。唉…………能读懂个鬼,强烈建议给msdn负责翻译的人员扣鸡腿。

上面大致的意思就是说,valuetask会避免同步情况下一些不必要的内存分配,从而提升应用整体的性能。

所以说,现在就能明白valuetask出现的目的是为了提升性能,而被提升的对象就是task。二位秋名山车神的竞速之路:

【5min+】 秋名山的竞速。 ValueTask 和 Task

如果您足够仔细,您会发现我上面说的是同步的情况。 “???纳尼,我用task不是异步吗?怎么成同步了?”

别急,回想下您是否写过这样的代码:

return task.fromresult(42);

您肯定写过(就算没写过也看过