在 C# 中利用 ValueTask 避免从异步方法返回 Task 对象时分配

翻译自 Joydip Kanjilal 2020年7月6日 的文章 《How to use ValueTask in C#》

异步编程已经使用了相当长一段时间了。近年来,随着 asyncawait 关键字的引入,它变得更加强大。您可以利用异步编程来提高应用程序的响应能力和吞吐量。

C# 中异步方法的推荐返回类型是 Task。如果您想编写一个有返回值的异步方法,那么应该返回 Task<T>; 如果想编写事件处理程序,则可以返回 void。在 C# 7.0 之前,异步方法可以返回 TaskTask<T>void。从 C# 7.0 开始,异步方法还可以返回 ValueTask(作为 System.Threading.Tasks.Extensions 包的一部分可用)或 ValueTask<T>。本文就讨论一下如何在 C# 中使用 ValueTask

要使用本文提供的代码示例,您的系统中需要安装 Visual Studio 2019。如果还没有安装,您可以在这里下载 Visual Studio 2019

在 Visual Studio 中创建一个 .NET Core 控制台应用程序项目

首先,让我们在 Visual Studio 中创建一个 .NET Core 控制台应用程序项目。假设您的系统中安装了 Visual Studio 2019,请按照下面描述的步骤在 Visual Studio 中创建一个新的 .NET Core 控制台应用程序项目。

  1. 启动 Visual Studio IDE。
  2. 点击 “创建新项目”。
  3. 在 “创建新项目” 窗口中,从显示的模板列表中选择 “控制台应用(.NET Core)”。
  4. 点击 “下一步”。
  5. 在接下来显示的 “配置新项目” 窗口,指定新项目的名称和位置。
  6. 点击 “创建”。

这将在 Visual Studio 2019 中创建一个新的 .NET Core 控制台应用程序项目。我们将在本文后面的部分中使用这个项目来说明 ValueTask 的用法。

为什么要使用 ValueTask ?

Task 表示某个操作的状态,即此操作是否完成、取消等。异步方法可以返回 Task 或者 ValueTask

现在,由于 Task 是一个引用类型,从异步方法返回一个 Task 对象意味着每次调用该方法时都会在托管堆(managed heap)上分配该对象。因此,在使用 Task 时需要注意的一点是,每次从方法返回 Task 对象时都需要在托管堆中分配内存。如果你的方法执行的操作的结果立即可用或同步完成,则不需要这种分配,因此代价很高。

这正是 ValueTask 要出手相助的目的,ValueTask<T> 提供了两个主要好处。首先,ValueTask<T> 提高了性能,因为它不需要在堆(heap)中分配; 其次,它的实现既简单又灵活。当结果立即可用时,通过从异步方法返回 ValueTask<T> 代替 Task<T>,你可以避免不必要的分配开销,因为这里的 “T” 表示一个结构,而 C# 中的结构体(struct)是一个值类型(与 Task<T> 中表示类的 “T” 不同)。

C# 中 TaskValueTask 表示两种主要的 “可等待(awaitable)” 类型。请注意,您不能阻塞(block)一个 ValueTask。如果需要阻塞,则应使用 AsTask 方法将 ValueTask 转换为 Task,然后在该引用 Task 对象上进行阻塞。

另外请注意,每个 ValueTask 只能被消费(consumed)一次。这里的单词 “消费(consume)” 是指 ValueTask 可以异步等待(await)操作完成,或者利用 AsTaskValueTask 转换为 Task。但是,ValueTask 只应被消费(consumed)一次,之后 ValueTask<T> 应被忽略。

C# 中的 ValueTask 示例

假设有一个异步方法返回一个 Task。你可以利用 Task.FromResult 创建 Task 对象,如下面给出的代码片段所示。

public Task<int> GetCustomerIdAsync()
{
return Task.FromResult(1);
}

上面的代码片段并没有创建整个异步状态机制,但它在托管堆(managed heap)中分配了一个 Task 对象。为了避免这种分配,您可能希望利用 ValueTask 代替,像下面给出的代码片段所示的那样。

public ValueTask<int> GetCustomerIdAsync()
{
return new ValueTask<int>(1);
}

下面的代码片段演示了 ValueTask 的同步实现。

public interface IRepository<T>
{
ValueTask<T> GetData();
}

Repository 类扩展了 IRepository 接口,并实现了如下所示的方法。

public class Repository<T> : IRepository<T>
{
public ValueTask<T> GetData()
{
var value = default(T);
return new ValueTask<T>(value);
}
}

下面是如何从 Main 方法调用 GetData 方法。

static void Main(string[] args)
{
IRepository<int> repository = new Repository<int>();
var result = repository.GetData();
if (result.IsCompleted)
Console.WriteLine("Operation complete...");
else
Console.WriteLine("Operation incomplete...");
Console.ReadKey();
}

现在让我们将另一个方法添加到我们的存储库(repository)中,这次是一个名为 GetDataAsync 的异步方法。以下是修改后的 IRepository 接口的样子。

public interface IRepository<T>
{
ValueTask<T> GetData(); ValueTask<T> GetDataAsync();
}

GetDataAsync 方法由 Repository 类实现,如下面给出的代码片段所示。

public class Repository<T> : IRepository<T>
{
public ValueTask<T> GetData()
{
var value = default(T);
return new ValueTask<T>(value);
} public async ValueTask<T> GetDataAsync()
{
var value = default(T);
await Task.Delay(100);
return value;
}
}

C# 中应该在什么时候使用 ValueTask ?

尽管 ValueTask 提供了一些好处,但是使用 ValueTask 代替 Task 有一定的权衡。ValueTask 是具有两个字段的值类型,而 Task 是具有单个字段的引用类型。因此,使用 ValueTask 意味着要处理更多的数据,因为方法调用将返回两个数据字段而不是一个。另外,如果您等待(await)一个返回 ValueTask 的方法,那么该异步方法的状态机也会更大,因为它必须容纳一个包含两个字段的结构体而不是在使用 Task 时的单个引用。

此外,如果异步方法的使用者使用 Task.WhenAll 或者 Task.WhenAny,在异步方法中使用 ValueTask<T> 作为返回类型可能会代价很高。这是因为您需要使用 AsTask 方法将 ValueTask<T> 转换为 Task<T>,这会引发一个分配,而如果使用起初缓存的 Task<T>,则可以轻松避免这种分配。

经验法则是这样的:当您有一段代码总是异步的时,即当操作(总是)不能立即完成时,请使用 Task。当异步操作的结果已经可用时,或者当您已经缓存了结果时,请利用 ValueTask。不管怎样,在考虑使用 ValueTask 之前,您都应该执行必要的性能分析。

ValueTaskreadonly struct 类型,Taskclass 类型。

相关链接:C# 中 Struct 和 Class 的区别总结

作者 : Joydip Kanjilal

译者 : 技术译民

出品 : 技术译站

链接 : 英文原文

如何使用 C# 中的 ValueTask的更多相关文章

  1. 理解C#中的ValueTask

    原文:https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/ 作者:Ste ...

  2. [周译见] C# 7 中的模范和实践

    原文地址:https://www.infoq.com/articles/Patterns-Practices-CSharp-7 关键点 遵循 .NET Framework 设计指南,时至今日,仍像十年 ...

  3. [No0000159]C# 7 中的模范和实践

    关键点 遵循 .NET Framework 设计指南,时至今日,仍像十年前首次出版一样适用. API 设计至关重要,设计不当的API大大增加错误,同时降低可重用性. 始终保持"成功之道&qu ...

  4. 深入理解 ValueTask

    深入理解 ValueTask .NET Framework 4 里面的命名空间为 System.Threading.Tasks的 Task 类.这个类以及它派生的 Task<TResult> ...

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

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

  6. 【C# Task】 ValueTask/Task<TResult>

    概要 1.如果异步方法的使用者使用 Task.WhenAll 或 Task.WhenAny,则在异步方法中使用 ValueTask<T> 作为返回类型可能会产生高昂的成本.这是因为您需要使 ...

  7. C#7模范和实践

    C# 7 中的模范和实践   原文地址:https://www.infoq.com/articles/Patterns-Practices-CSharp-7 关键点 遵循 .NET Framework ...

  8. 【5min+】你怎么穿着品如的衣服?IEnumerable AND IEnumerator

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

  9. Python开源框架

    info:更多Django信息url:https://www.oschina.net/p/djangodetail: Django 是 Python 编程语言驱动的一个开源模型-视图-控制器(MVC) ...

随机推荐

  1. springMVC入门(七)------RESTFul风格的支持

    简介 RESTful风格(Representational State Transfer),又叫表现层状态转移,是一种开发理念,也是对HTTP协议很好的诠释 主要理念是将互联网中的网页.数据.服务都视 ...

  2. docker 启动容器失败 id already in use

    问题:id already in use 解决:/etc/docker/daemon.json {"shutdown-timeout": 60}

  3. 计算机网络-传输层(3)TCP协议与拥堵控制

    TCP是因特网传输层的面向连接的可靠的运输协议.   TCP被称为是面向连接的:通信双方在发送数据之前必须建立连接,连接状态只在连接的两端中维护,在沿途节点中并不维护状态. TCP连接包括:两台主机上 ...

  4. “DNAT+云链接+CDN”加速方案,助力出海企业落地生长

    摘要:“DNAT+云链接+CDN”加速方案,真正释放技术红利,真诚助力企业出海. 随着国内互联网行业的人口红利逐渐消失,本土互联网市场竞争不断加剧,加之国家多项“走出去”政策的推动,越来越多的中国互联 ...

  5. 分享一个登录页面(前端框架layui)-20200318

    效果图 对该页面的总结: 1.前端框架layui layui官网:https://www.layui.com/,下载之后,简单配置就可使用 2.layui模块引用与使用的方式 <script&g ...

  6. Java/后端学习路线

    点赞再看,养成习惯,微信搜一搜[三太子敖丙]关注这个喜欢写情怀的程序员. 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点.资料以及我的系 ...

  7. 解决 SQL 注入和 XSS 攻击(Node.js 项目中)

    1.SQL 注入 SQL 注入,一般是通过把 SQL 命令插入到 Web 表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的 SQL 命令. SQL 注入示例 在登录界面,后端会根 ...

  8. Python的UI库

    https://github.com/realitix/vulkan https://github.com/swistakm/pyimgui https://wxpython.org

  9. Unity可编程管线的顶点光照Shader

    UnityCG.cginc有一个叫ShadeVertexLightsFull的函数可以用来计算顶点光照. 源码如下: // Used in Vertex pass: Calculates diffus ...

  10. 利用阿里云服务器免费体验word press博客、个人网站

    本文首发于我的个人博客:https://chens.life/create-wordpress-blog.html 前言 目前市面上有许许多多的虚拟云服务器ECS,例如阿里云.华为云.又拍云等等,他们 ...