如何使用 C# 中的 ValueTask
在 C# 中利用 ValueTask
避免从异步方法返回 Task
对象时分配
翻译自 Joydip Kanjilal 2020年7月6日 的文章 《How to use ValueTask in C#》
异步编程已经使用了相当长一段时间了。近年来,随着 async
和 await
关键字的引入,它变得更加强大。您可以利用异步编程来提高应用程序的响应能力和吞吐量。
C# 中异步方法的推荐返回类型是 Task
。如果您想编写一个有返回值的异步方法,那么应该返回 Task<T>
; 如果想编写事件处理程序,则可以返回 void
。在 C# 7.0 之前,异步方法可以返回 Task
、Task<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 控制台应用程序项目。
- 启动 Visual Studio IDE。
- 点击 “创建新项目”。
- 在 “创建新项目” 窗口中,从显示的模板列表中选择 “控制台应用(.NET Core)”。
- 点击 “下一步”。
- 在接下来显示的 “配置新项目” 窗口,指定新项目的名称和位置。
- 点击 “创建”。
这将在 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# 中 Task
和 ValueTask
表示两种主要的 “可等待(awaitable)” 类型。请注意,您不能阻塞(block)一个 ValueTask
。如果需要阻塞,则应使用 AsTask
方法将 ValueTask
转换为 Task
,然后在该引用 Task
对象上进行阻塞。
另外请注意,每个 ValueTask
只能被消费(consumed)一次。这里的单词 “消费(consume)” 是指 ValueTask
可以异步等待(await
)操作完成,或者利用 AsTask
将 ValueTask
转换为 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
之前,您都应该执行必要的性能分析。
ValueTask
是readonly struct
类型,Task
是class
类型。
相关链接:C# 中 Struct 和 Class 的区别总结。
作者 : Joydip Kanjilal
译者 : 技术译民
出品 : 技术译站
链接 : 英文原文
如何使用 C# 中的 ValueTask的更多相关文章
- 理解C#中的ValueTask
原文:https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/ 作者:Ste ...
- [周译见] C# 7 中的模范和实践
原文地址:https://www.infoq.com/articles/Patterns-Practices-CSharp-7 关键点 遵循 .NET Framework 设计指南,时至今日,仍像十年 ...
- [No0000159]C# 7 中的模范和实践
关键点 遵循 .NET Framework 设计指南,时至今日,仍像十年前首次出版一样适用. API 设计至关重要,设计不当的API大大增加错误,同时降低可重用性. 始终保持"成功之道&qu ...
- 深入理解 ValueTask
深入理解 ValueTask .NET Framework 4 里面的命名空间为 System.Threading.Tasks的 Task 类.这个类以及它派生的 Task<TResult> ...
- 【5min+】 秋名山的竞速。 ValueTask 和 Task
系列介绍 简介 [五分钟的dotnet]是一个利用您的碎片化时间来学习和丰富.net知识的博文系列.它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的. ...
- 【C# Task】 ValueTask/Task<TResult>
概要 1.如果异步方法的使用者使用 Task.WhenAll 或 Task.WhenAny,则在异步方法中使用 ValueTask<T> 作为返回类型可能会产生高昂的成本.这是因为您需要使 ...
- C#7模范和实践
C# 7 中的模范和实践 原文地址:https://www.infoq.com/articles/Patterns-Practices-CSharp-7 关键点 遵循 .NET Framework ...
- 【5min+】你怎么穿着品如的衣服?IEnumerable AND IEnumerator
系列介绍 简介 [五分钟的dotnet]是一个利用您的碎片化时间来学习和丰富.net知识的博文系列.它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的. ...
- Python开源框架
info:更多Django信息url:https://www.oschina.net/p/djangodetail: Django 是 Python 编程语言驱动的一个开源模型-视图-控制器(MVC) ...
随机推荐
- CentOS7 更改默认启动桌面(或命令行)模式
centos7以后是这样的,7以前就是别的版本了 1.systemctl get-default命令获取当前模式 2.systemctl set-default graphical.target 修改 ...
- python格式化输出及大量案例
python格式化输出符号及大量案例 1.格式化输出符号 python格式化输出符号 格式化符号 含义 %c 转化成字符 %r 优先使用repr()函数进行字符串转化 %s 转换成字符串,优先使用st ...
- 修改docker0网桥的IP段
关闭docker进程 systemctl stop docker 修改/etc/docker/daemon.json { "bip": "100.96.2.1/24&qu ...
- Vue基础(二)---- 常用特性
常用特性分类: 表单操作 自定义指令 计算属性 侦听器 过滤器 生命周期 补充知识(数组相关API) 案例:图书管理 1.表单操作 基于Vue的表单操作:主要用于向后台传递数据 Input 单行文本 ...
- 让“不确定性”变得有“弹性”?基于弹性容器的AI评测实践
0. 前言 AI的场景丰富多彩,AI的评价方法百花齐放,这对于设计一套更通用的评测框架来说,是一个极大的挑战,需要兼顾不同的协议,不同的模型环境,甚至是不同的操作系统.本文分享了我们在AI评测路上的一 ...
- [ASP.NET Core开发实战]基础篇02 依赖注入
ASP.NET Core的底层机制之一是依赖注入(DI)设计模式,因此要好好掌握依赖注入的用法. 什么是依赖注入 我们看一下下面的例子: public class MyDependency { pub ...
- GENYMOTION问题之an error occurred while deploying a file install_failed_no_machine_abis
GENYMOTION问题之an error occurred while deploying a file install_failed_no_machine_abis 出现上面错误,看网上有一种解决 ...
- Tesselation学习
Tesselation的作用:给低片面数模型镶嵌更多片面,让低模变高模. 和法线贴图不同,法线本质是通过改变低模表面的颜色来模拟高模,比如在一个片面上普通diffuse是均匀的颜色分布(因为光照颜色一 ...
- webapi上传图片的两种方式
/// <summary> /// App上传图片 /// </summary> /// <returns>返回上传图片的 ...
- Kubernetes 存活、就绪探针
在设计关键任务.高可用应用程序时,弹性是要考虑的最重要因素之一. 当应用程序可以快速从故障中恢复时,它便具有弹性. 云原生应用程序通常设计为使用微服务架构,其中每个组件都位于容器中.为了确保Kuber ...