Task VS ValueTask

在 C# 中,异步编程是构建响应式应用程序的基础。Task 是表示异步操作的首选类型。但是,在某些高性能场景中,与 Task 相关的开销可能会达到一个瓶颈。ValueTask 是 .NET Core 2.1 中引入的结构。与引用类型的 Task 不同,ValueTask 是一种值类型,这使得它在某些情况下效率更高,尤其是在异步操作通常同步完成时。

1. Task 的特点

定义

  • Task 是 C# 中表示异步操作的基础类型。
  • • 它是一个引用类型,用于表示一个可能尚未完成的异步操作。

适用场景

  • • 适用于大多数异步操作,尤其是那些可能需要较长时间完成的操作(如 I/O 操作、网络请求等)。
  • • 当异步操作的结果可能不会立即完成时,Task 是一个通用的选择。

优点

  • • 功能强大,支持复杂的异步操作。
  • • 可以表示没有返回值(Task)和有返回值(Task<T>)的异步操作。
  • • 支持任务组合(如 Task.WhenAllTask.WhenAny)。

缺点

  • • 由于是引用类型,每次创建 Task 都会在堆上分配内存,可能对性能产生一定影响,尤其是在高频调用的场景中。

2. ValueTask 的特点

定义

  • ValueTask 是 C# 7.0 引入的一种轻量级的异步操作类型。
  • • 它是一个值类型,用于表示可能同步完成或异步完成的操作。

适用场景

  • • 适用于高频调用的异步操作,尤其是那些可能经常同步完成的操作。
  • • 当异步操作的结果可能立即完成时,ValueTask 可以避免不必要的堆分配,从而提高性能。

优点

  • • 由于是值类型,ValueTask 在栈上分配内存,避免了堆分配的开销。
  • • 在同步完成的场景中,性能优于 Task
  • • 支持与 Task 相同的功能,如 await 和异步操作组合。

缺点

  • • 功能相对简单,不适合复杂的异步操作(均不支持任务组合、取消操作、任务状态等等)。
  • • 由于是值类型,不能为 null,且不能直接转换为 Task

3. ValueTaskTask 的区别

特性 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. 1. TResult
    • • 用于存储同步操作的结果值。
  2. 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 是更通用的选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/889993.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EtherNet/IP转Modbus看网关模块驱动罗克韦尔PLC与上位机协议转换全过程

在工业自动化控制系统中,常常会遇到不同品牌和通信协议的设备需要协同工作的情况。本案例中,客户现场采用了 AB PLC,但需要控制的变频器仅支持 Modbus 协议。为了实现 AB PLC 对变频器的有效控制与监控,引入了捷米特 JM-EIP-RTU 网关模块来完成 EtherNet/IP 与 Modbus 之间…

Joker 可视化开发平台全局方法使用指南

在 Joker 可视化开发平台中,全局方法是实现公共业务逻辑的有力工具,它能跨越组件和页面文件的界限,让开发者快速调用,显著提升开发效率。下面将详细介绍全局方法在平台中的使用方式。 一、全局方法的定义与功能全局方法是用于封装通用业务逻辑的特殊函数,在整个项目中都可…

Excel数据快速入库

我们在日常的开发过程中,经常会需要将业务端的Excel数据,导入到数据库。 可以用一个简单的方式:插入的SQL语句:INSERT INTO students (id, name, age) VALUES (2, Jane Smith, 22), (3, Emily Jones, 21), (4, Michael Brown, 23);然后整理Excel: 这里需要将【双引号】替…

没有源码,如何修改代码逻辑?

拍摄于西安德福巷前段时间接手了一个二次开发其他团队代码的项目,过程中发生了不少有意思的小插曲。今天正好有点空闲时间,就简单梳理一下,希望能给大家提供一些参考和帮助。当我咨询对方团队:“大哥,我们这边要对你们在xxx项目上的代码进行二次开发,想了解下你们的二开机…

deepseek本地部署硬件资源对比表.250226

‌DeepSeek在不同版本下的硬件资源需求对比表如下‌:版本 CPU核心数 GPU显存(GB) GPU类型 内存(GB) 存储需求(TB) 网络带宽(Mbps)1.5B 1-2 4 普通消费级(如RTX 3090/4090) 8-16 1 50-1007B 1-2 4 普通消费级(如RTX 3090/4090) 8-16 1 50-1008B 1-2 4 普通消费级(…

deepseek本地部署硬件资源对比表。250226

‌DeepSeek在不同版本下的硬件资源需求对比表如下‌:版本 CPU核心数 GPU显存(GB) GPU类型 内存(GB) 存储需求(TB) 网络带宽(Mbps)1.5B 1-2 4 普通消费级(如RTX 3090/4090) 8-16 1 50-1007B 1-2 4 普通消费级(如RTX 3090/4090) 8-16 1 50-1008B 1-2 4 普通消费级(…

用python画一个五星红旗

import turtle 设置画布和画笔 screen = turtle.Screen() 设置画布大小 screen.setup(800, 600) 设置画布背景颜色为红色,模拟五星红旗旗面颜色 screen.bgcolor("red") pen = turtle.Turtle() 设置画笔最快速度 pen.speed(0) 隐藏画笔箭头 pen.hideturtle() 定义绘制…

爬取东方财富网-parsel教学篇(正则表达式的详细使用+实例)

@目录前言导航正则表达式介绍正则表达式基本语法re库的使用常用函数案例源码运行截图共勉博客 前言 本文原本是想通过分享一个爬取东方财富网案例,来介绍parsel解析库的使用,没想到硬生生的写成了正则表达式的详细使用,想学习正则表达式的的小伙伴们可以来看下。 导航爬取小…

如何通过网管系统提升运维效率?

网络系统在企业信息化系统扮演着越来越重要的作用,网络规模不断扩大,网络结构越来越复杂,传统的运维方式已经难以满足高效、稳定运行的要求。网管系统作为IT运维的重要工具,能够帮助企业实现网络的智能化管理,显著提升运维效率。本文将探讨网管系统与IT运维的关系,并结合…

保证接口幂等性的这 7 种方案,绝了!

前言 接口幂等性问题,对于开发人员来说,是一个跟语言无关的公共问题。本文分享了一些解决这类问题非常实用的办法,绝大部分内容我在项目中实践过的,给有需要的小伙伴一个参考。 不知道你有没有遇到过这些场景:我们在填写某些form表单时,保存按钮不小心快速点了两次,表中…

毕设的踩坑之路

main()函数 在main()函数中调用 QMessageBox 之前一定要创建 QApplication 对象, 使用 QWidget 之前要创建 QApplication 对象. 不然会程序崩溃. 下面是笔者原先的代码: 之前是因为数据库等一切正常, 所以没有触发到连接数据库失败的 QMessageBox 消息. 后来有一次连接的时候数…

表格内cron表达式转成需要的时间格式

1.表格内添加 :formatter=""<el-table-columnlabel="监视周期"align="center"prop="corn":formatter="cornFormat"/>2.方法// 频率corn转时间格式cornFormat(row, column) {let str = row.corn;// let str = row.corn…