在 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. 计算机网络-应用层(5)P2P应用

    P2P系统的索引:信息到节点位置(IP地址+端口号)的映射 在文件共享(如电驴中):利用索引动态跟踪节点所共享的文件的位置.节点需要告诉索引它拥有哪些文件.节点搜索索引从而获知能够得到哪些文件 在即时 ...

  2. 【转】camera tuning

    chromatix项目必须包含有效的 ADC image.png filcker: 交流电照明灯发出的光会一定频率的抖动导致sensor图像出现行方向的水波纹,称之为filcker.目前主要的交流电频 ...

  3. 7.深入k8s:任务调用Job与CronJob及源码分析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 在使用job中,我会结合源码进行一定的讲解,我们也可以从源码中一窥究竟,一些细节k8s是 ...

  4. muduo源码解析5-mutex相关类

    mutexlock和mutexlockguard class mutexlock:noncopyable { }: class mutexlockguard:noncopyable { }: 作用: ...

  5. 多线程std::cout 深入研究

    1.研究背景 在测试时发现mingw版本的gcc编译出来的程序,一个主程序新建20个线程,每个线程都循环向cout输出信息,几分钟程序就崩了,而用msvc和gcc-linaro版gcc交叉编译器编译出 ...

  6. Tensorflow2(二)tf.data输入模块

    代码和其他资料在 github 一.tf.data模块 数据分割 import tensorflow as tf dataset = tf.data.Dataset.from_tensor_slice ...

  7. SCE信号期末复习省流小助手(懒人版)

    XDU-SCE网信院信号期末复习省流小助手(懒人版) 本人根据西安电子科技大学网络与信息安全18年期末考试整理的考点和题型 以下题型代表了信号与系统课程的 精髓 若能掌握以下知识点和题型,80分稳有: ...

  8. 字段在class文件中的存在形式——FieldInfo

    每个字段(Field)都有field_info结构所定义,一个class文件中,不会有两个字段同时具有相同的名字和描述符 name_index:值为一个整数(常量池表中的有效索引),例如name_in ...

  9. zero:seo优化的三部曲

    http://www.wocaoseo.com/thread-230-1-1.html 理解用户的需求.了解搜索引擎的原理.明白如何通过搜索引擎优化对网站产生价值,这是SEO学习中应该陆续深入的三个部 ...

  10. Ellxir

    API: elixir https://hexdocs.pm/elixir/Module.html#content API: erlang http://www.cnerlang.com/api.ht ...