Task VS ValueTask
在 C# 中,异步编程是构建响应式应用程序的基础。Task 是表示异步操作的首选类型。但是,在某些高性能场景中,与 Task 相关的开销可能会达到一个瓶颈。ValueTask 是 .NET Core 2.1 中引入的结构。与引用类型的 Task 不同,ValueTask 是一种值类型,这使得它在某些情况下效率更高,尤其是在异步操作通常同步完成时。
1. Task 的特点
定义
- •
Task是 C# 中表示异步操作的基础类型。 - • 它是一个引用类型,用于表示一个可能尚未完成的异步操作。
适用场景
- • 适用于大多数异步操作,尤其是那些可能需要较长时间完成的操作(如 I/O 操作、网络请求等)。
- • 当异步操作的结果可能不会立即完成时,
Task是一个通用的选择。
优点
- • 功能强大,支持复杂的异步操作。
- • 可以表示没有返回值(
Task)和有返回值(Task<T>)的异步操作。 - • 支持任务组合(如
Task.WhenAll、Task.WhenAny)。
缺点
- • 由于是引用类型,每次创建
Task都会在堆上分配内存,可能对性能产生一定影响,尤其是在高频调用的场景中。
2. ValueTask 的特点
定义
- •
ValueTask是 C# 7.0 引入的一种轻量级的异步操作类型。 - • 它是一个值类型,用于表示可能同步完成或异步完成的操作。
适用场景
- • 适用于高频调用的异步操作,尤其是那些可能经常同步完成的操作。
- • 当异步操作的结果可能立即完成时,
ValueTask可以避免不必要的堆分配,从而提高性能。
优点
- • 由于是值类型,
ValueTask在栈上分配内存,避免了堆分配的开销。 - • 在同步完成的场景中,性能优于
Task。 - • 支持与
Task相同的功能,如await和异步操作组合。
缺点
- • 功能相对简单,不适合复杂的异步操作(均不支持任务组合、取消操作、任务状态等等)。
- • 由于是值类型,不能为
null,且不能直接转换为Task。
3. ValueTask 和 Task 的区别
| 特性 | Task |
ValueTask |
|---|---|---|
| 类型 | 引用类型(class) | 值类型(struct) |
| 内存分配 | 堆分配 | 栈分配(在同步完成时) |
| 性能 | 适用于大多数场景,但可能有堆分配开销 | 在高频调用或同步完成时性能更优 |
| 适用场景 | 通用异步操作 | 高频调用或可能同步完成的异步操作 |
| 复杂性 | 功能强大,支持复杂操作 | 功能相对简单 |
是否可为 null |
可以 | 不可以 |
4. 举例说明
从缓存中读取数据
假设有一个方法,尝试从缓存中读取数据。如果缓存中有数据,则直接返回;如果没有,则从数据库异步获取数据并缓存。
使用 Task 的实现
public async Task<ProductDto> GetProductAsync(int productId)
{
var key = $"Product_{productId}";
// 尝试从缓存中同步获取数据
if (_memoryCache.TryGetValue(key, out var cachedData))
{
return cachedData; // 如果数据在缓存中,直接返回
}
// 如果数据不在缓存中,异步获取数据并缓存
var data = await _productRepo.GetDataAsync(productId);
_memoryCache.Set(key, data, TimeSpan.FromMinutes(60)); // 设置缓存过期时间
return data;
}
- • 问题:
- • 即使缓存命中(同步操作),
Task也会在堆上分配内存。 - • 如果缓存命中率很高,频繁的内存分配会影响性能。
- • 即使缓存命中(同步操作),
使用 ValueTask 的实现
public async ValueTask<ProductDto> GetProductAsync(int productId)
{
var key = $"Product_{productId}";
// 尝试从缓存中同步获取数据
if (_memoryCache.TryGetValue(key, out var cachedData))
{
return cachedData; // 如果数据在缓存中,直接返回
}
// 如果数据不在缓存中,异步获取数据并缓存
var data = await _productRepo.GetDataAsync(productId);
_memoryCache.Set(key, data, TimeSpan.FromMinutes(60)); // 设置缓存过期时间
return data;
}
- • 优点:
- • 如果缓存命中(同步操作),
ValueTask不会在堆上分配内存,性能更高。 - • 如果缓存未命中(异步操作),
ValueTask会退化为Task,性能与Task相同。
- • 如果缓存命中(同步操作),
ValueTask 的内部结构主要由以下两部分组成:

- 1.
TResult:- • 用于存储同步操作的结果值。
- 2.
Task<TResult>或IValueTaskSource<TResult>:- • 用于表示异步操作的任务。
通过这种设计,ValueTask 可以根据操作的实际完成方式(同步或异步)动态选择最合适的实现方式。
5.如何选择
| 场景 | 推荐类型 | 原因 |
|---|---|---|
| 大多数异步操作(如 I/O 操作) | Task |
代码简单,易于理解。 |
| 高频调用(如缓存读取) | ValueTask |
减少内存分配,提升性能。 |
| 可能同步完成的操作 | ValueTask |
同步完成时不会分配堆内存。 |
| 长时间运行的操作 | Task |
Task更适合长时间运行的异步操作。 |
需要多次 await的操作 |
Task |
ValueTask不能多次 await |
6. 注意事项
Task 的注意事项
- • 内存分配:
- • 每次调用都会在堆上分配内存,即使操作是同步完成的。
- • 简单性:
- • 代码更易于理解和维护。
ValueTask 的注意事项
- • 不能多次
await:- •
ValueTask只能被await一次,如果需要多次等待,应先转换为Task。 - • 例如:
await (await GetProductAsync()).ConfigureAwait(false);是不允许的。
- •
- • 复杂性:
- • 需要更多注意,避免误用。
- • 性能优化:
- • 只有在高频调用或可能同步完成的场景下,
ValueTask的性能优势才明显。
- • 只有在高频调用或可能同步完成的场景下,
7.总结
- •
Task:- • 适用于大多数异步场景,代码简单易用。
- • 每次调用都会在堆上分配内存。
- •
ValueTask:- • 适用于高频调用或可能同步完成的场景,性能更高。
- • 需要更多注意,避免误用。
根据你的具体需求选择合适的类型。如果性能是关键,且缓存命中率较高,推荐使用 ValueTask;否则,使用 Task 是更通用的选择。

Task VS ValueTask的更多相关文章
- 【C# Task】 ValueTask/Task<TResult>
概要 1.如果异步方法的使用者使用 Task.WhenAll 或 Task.WhenAny,则在异步方法中使用 ValueTask<T> 作为返回类型可能会产生高昂的成本.这是因为您需要使 ...
- 一个 Task 不够,又来一个 ValueTask ,真的学懵了!
一:背景 1. 讲故事 前几天在项目中用 MemoryStream 的时候意外发现 ReadAsync 方法多了一个返回 ValueTask 的重载,真是日了狗了,一个 Task 已经够学了,又来一个 ...
- 如何使用 C# 中的 ValueTask
在 C# 中利用 ValueTask 避免从异步方法返回 Task 对象时分配 翻译自 Joydip Kanjilal 2020年7月6日 的文章 <How to use ValueTask i ...
- 【C# Task】开篇
概览 在学task类之前必须学习线程的知识. 以下是task命名空间的类的结构图 1.2种任务类型: 有返回值task<TResult> .无返回值task. 2.2座任务工厂 TaskF ...
- async/await使用深入详解
async和await作为异步模型代码编写的语法糖已经提供了一段时间不过一直没怎么用,由于最近需要在BeetleX webapi中集成对Task方法的支持,所以对async和await有了深入的了解和 ...
- .NET中的值类型与引用类型
.NET中的值类型与引用类型 这是一个常见面试题,值类型(Value Type)和引用类型(Reference Type)有什么区别?他们性能方面有什么区别? TL;DR(先看结论) 值类型 引用类型 ...
- Orleans 知多少 | 3. Hello Orleans
1. 引言 是的,Orleans v3.0.0 已经发布了,并已经完全支持 .NET Core 3.0. 所以,Orleans 系列是时候继续了,抱歉,让大家久等了. 万丈高楼平地起,这一节我们就先来 ...
- 异步编程,await async入门
网上很多异步编程的文章,提供一篇入门: 异步编程模型 .net支持3种异步编程模式: msdn:https://docs.microsoft.com/zh-cn/dotnet/standard/asy ...
- 你所不知道的 C# 中的细节
前言 有一个东西叫做鸭子类型,所谓鸭子类型就是,只要一个东西表现得像鸭子那么就能推出这玩意就是鸭子. C# 里面其实也暗藏了很多类似鸭子类型的东西,但是很多开发者并不知道,因此也就没法好好利用这些东西 ...
- .NET 异步详解
前言 博客园中有很多关于 .NET async/await 的介绍,但是很遗憾,很少有正确的,甚至说大多都是"从现象编原理"都不过分. 最典型的比如通过前后线程 ID 来推断其工作 ...
随机推荐
- [.NET Blog] .NET Aspire 测试入门
https://devblogs.microsoft.com/dotnet/getting-started-with-testing-and-dotnet-aspire/ 自动化测试是软件开发的重要一 ...
- 中电金信:数字经济时代,AI+金融技术应用与未来发展
- 刷到一个 MLSQL 语言
在 https://www.infoq.cn/video/2vFUBYfxFcoFWmSm5WOj 刷到一个 MLSQL 语言,主页 https://www.mlsql.tech/home ,意思是用 ...
- 【数据库】【MySQL】创建数据库、数据表、修改数据表字段的一些基本操作
这是一份作业,仅此而已. 代码仅供参考. # 创建数据库yggl_mllt9920 CREATE DATABASE yggl_mllt9920; # 选择数据库 USE yggl_mllt9920; ...
- UML之模型、包及包的版型(构造型)
包是UML模型的组织结构,也是UML项目的配置管理结构.包存在多个层级,除了顶层包,所有包隶属于一个且仅隶属于一个上层包.在项目不同阶段实际推进与配置过程中,通常以不同层级的包为单位进行check-i ...
- 《Spring Boot+Vue全栈开发实战-王松2018》一书pdf+源码下载
下载地址为: 链接:https://pan.baidu.com/s/18lnF2KemQTqkKaCRmMbvXA 提取码:1pie 版权声明:本书版权属于出版社和作者.仅学习使用,请于下载后24小时 ...
- blip2代码解析
请你作为一个代码翻译解释生成器,下面我会发送一个github链接给你,请你详细解析与介绍这个链接下代码 好的,请发送链接. https://github.com/huggingface/transfo ...
- 在linux上Git配置多个SSH-Key
Git配置多个SSH-Key SSH Key 背景 当有多个git账号时,比如: a. 一个gitee,用于公司内部的工作开发: b. 一个github,用于自己进行一些开发活动: c.一个gitla ...
- Solution Set -「NOIP Simu.」20221010
会不会组题啊? 希望 trash round 少来点. 「Unkown」构造字符串 给定 \(m\) 组形如 \((x,y,z)\), 表示 \(\operatorname{lcp}(S[x: ...
- ofd轻阅读---采用Typescript全新开发,让阅读、批注更方便!
前言 浏览器内核已支持pdf文件的渲染,这极大的方便了pdf文件的阅读和推广.ofd文件作为国产板式标准,急需一套在浏览器中渲染方案. 本人研究ofd多年,分别采用qt.c# 开发了ofd阅读器.本 ...