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

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. vscode实现Markdown实时预览

    vscode - 插件搜索: Markdown Preview Enhanced 安装 然后打开vscode预览框,即可. That's ALL

  2. Gitbook的docker安装配置

    创建目录:/gitbook/gitbook 和 /gitbook/html /gitbook/gitbook目录下,touch新建README.md docker安装gitbook docker ru ...

  3. Advanced .NET Remoting: 第 8 章 创建连接器

    第 8 章 创建连接器 上一章向您展示了各种类型的连接器,以及它们对于请求的同步和异步处理过程.到目前为止,我一直忽略的一个最为重要的步骤是:初始化连接器和连接器链.连接器通常既不是直接待代码中创建, ...

  4. opencv+Linux源码编译安装及引用

    (一)下载 opencv下载地址:https://opencv.org/releases/ opencv_contrib下载地址:https://github.com/opencv/opencv_co ...

  5. Http2服务调用排坑记

    原文作者:陈友行原文链接:https://www.nginx.org.cn/article/detail/89转载来源:NGINX开源社区著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明 ...

  6. 2024 Nuxt3 年度生态总结

    hello,大家好,我是程序员海军.很荣幸能与大家分享我今年的第三篇文章.在过去的一年里,我深入探索了Nuxt3,并在多个项目中实际应用了这一前沿框架,从而对其功能和应用有了全面而深刻的理解.今天,我 ...

  7. Windows10中安装了ubuntu虚拟机后xshell无法连接到ubuntu

    安装了ubuntu虚拟机后发现shell无法连接到ubuntu的排查步骤: 步骤 1: 检查虚拟机网络配置确认虚拟机网络模式:确认虚拟机的网络模式是否设置为桥接模式或NAT模式.桥接模式可以让你的虚拟 ...

  8. 网络编程入门从未如此简单(三):什么是IPv6?漫画式图文,一篇即懂!

    本文由小枣君分享,文案:小枣君.漫画:杨洋,来自鲜枣课堂,有少许改动,原文链接见文末. 1.引言 网络编程能力对于即时通讯技术开发者来说是基本功,而计算机网络又是网络编程的理论根基,因而深刻准确地理解 ...

  9. Scala,一门「特立独行」的语言!

    入门 Spark 的路上很难不接触 Scala . Scala 似乎是为 java 提供了很多『类似函数式编程』的语法糖,这里记录一下这个语言独特的地方分享给读者朋友们. 参考资料主要有: 曹洁 . ...

  10. 树莓派linux kernel 添加menuconfig菜单选项和编译

    https://www.bilibili.com/video/av91990721?zwbcmrpi_defconfig 打开顶层目录下的 Makefile,搜索 %config 找到如下内容: # ...