在 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. 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 是更通用的选择。

Task VS ValueTask的更多相关文章

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

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

  2. 一个 Task 不够,又来一个 ValueTask ,真的学懵了!

    一:背景 1. 讲故事 前几天在项目中用 MemoryStream 的时候意外发现 ReadAsync 方法多了一个返回 ValueTask 的重载,真是日了狗了,一个 Task 已经够学了,又来一个 ...

  3. 如何使用 C# 中的 ValueTask

    在 C# 中利用 ValueTask 避免从异步方法返回 Task 对象时分配 翻译自 Joydip Kanjilal 2020年7月6日 的文章 <How to use ValueTask i ...

  4. 【C# Task】开篇

    概览 在学task类之前必须学习线程的知识. 以下是task命名空间的类的结构图 1.2种任务类型: 有返回值task<TResult> .无返回值task. 2.2座任务工厂 TaskF ...

  5. async/await使用深入详解

    async和await作为异步模型代码编写的语法糖已经提供了一段时间不过一直没怎么用,由于最近需要在BeetleX webapi中集成对Task方法的支持,所以对async和await有了深入的了解和 ...

  6. .NET中的值类型与引用类型

    .NET中的值类型与引用类型 这是一个常见面试题,值类型(Value Type)和引用类型(Reference Type)有什么区别?他们性能方面有什么区别? TL;DR(先看结论) 值类型 引用类型 ...

  7. Orleans 知多少 | 3. Hello Orleans

    1. 引言 是的,Orleans v3.0.0 已经发布了,并已经完全支持 .NET Core 3.0. 所以,Orleans 系列是时候继续了,抱歉,让大家久等了. 万丈高楼平地起,这一节我们就先来 ...

  8. 异步编程,await async入门

    网上很多异步编程的文章,提供一篇入门: 异步编程模型 .net支持3种异步编程模式: msdn:https://docs.microsoft.com/zh-cn/dotnet/standard/asy ...

  9. 你所不知道的 C# 中的细节

    前言 有一个东西叫做鸭子类型,所谓鸭子类型就是,只要一个东西表现得像鸭子那么就能推出这玩意就是鸭子. C# 里面其实也暗藏了很多类似鸭子类型的东西,但是很多开发者并不知道,因此也就没法好好利用这些东西 ...

  10. .NET 异步详解

    前言 博客园中有很多关于 .NET async/await 的介绍,但是很遗憾,很少有正确的,甚至说大多都是"从现象编原理"都不过分. 最典型的比如通过前后线程 ID 来推断其工作 ...

随机推荐

  1. 【Amadeus原创】域用户完美执行应用程序

    企业环境中,为了安全起见一般都没有赋予域用户或者企业的PC客户端用户管理员权限. 但偶尔会有个别的程序一定需要管理员身份才能执行,如财务某些程序或专业的应用程序.那么如何不赋予用户管理员权限及密码但又 ...

  2. COSBrowser 文件夹分享——多端文件实时共享

    ​ 您还在为临时分享某个文件夹而烦恼吗? 您是否对授权的复杂度感到震惊? 关于存储桶 Policy 权限,您是否很迷茫,不知如何设置? 不用着急,用 COSBrowser 开始文件夹分享,一键简化分享 ...

  3. 【前端】【H5 API】拽托

    拖曳操作需要借助于鼠标来实现,如文件或图片的移动操作等.在开发中,我们经常使用原生的JavaScript来实现拖曳效果,实现起来比较复杂.因此,HTML5为我们提供了更好用的接口或者事件,在很大程度上 ...

  4. Qt编写可视化大屏电子看板系统32-模块10大屏地图

    一.前言 大屏地图模块采用浏览器模块+echart组件,Qt自带了webkit或者webengine模块,其中在win上mingw编译器的Qt5.6以后的版本,没有了浏览器模块,这个就需要用第三方的浏 ...

  5. Qt音视频开发11-ffmpeg常用命令

    一.前言 大部分的格式转换工具比如格式化工厂等,都用到了ffmpeg来处理,ffmpeg编译后生成的ffmpeg.exe.ffplay.exe.ffprobe.exe等可执行文件,其实就封装了众多牛逼 ...

  6. MySql 建表出现的问题 : [ERR] 1064 - You have an error in your SQL syntax; check the manual.......

    使用 MySql 建表出现的问题 在使用 Navicat Premium 运行 sql 语句进行建表时,MySQL 报错如下: 1064 - You have an error in your SQL ...

  7. 海量用户IM聊天室的架构设计与实践

    本文由网易云信资深服务端开发工程师曹佳俊分享,本文收录时有内容修订和重新排版. 1.引言 聊天室是一类非常重要的 IM 业务形态,不同于单聊和群聊,聊天室是一种大规模的实时消息分发系统.聊天室有多种技 ...

  8. # 个人认为windows下最好用的一批软件 #

    个人认为windows下最好用的一批软件 chrome 为什么是chrome而不是edge2? 这当然不是因为我是谷歌吹什么的.其实当得知edge2比chrome更省内存,运行更快,而且不跨越城墙就能 ...

  9. 《C++并发编程实战》读书笔记(3):并发操作的同步

    1.条件变量 当线程需要等待特定事件发生.或是某个条件成立时,可以使用条件变量std::condition_variable,它在标准库头文件<condition_variable>内声明 ...

  10. C# Moq - Non-overridable members may not be used in setup / verification expressions

    测试: public class UnitTest1 { [TestMethod] public void TestMethod1() { Mock<TestClass> moc = ne ...