在 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. 渗透测试-前端验签绕过之SHA256+RSA

    本文是高级前端加解密与验签实战的第2篇文章,本系列文章实验靶场为Yakit里自带的Vulinbox靶场,本文讲述的是绕过SHA256+RSA签名来爆破登录. 绕过 根据提示可以看出这次签名用了SHA2 ...

  2. 【Amadeus原创】Docker安装最新版wordpress

    0.安装docker curl -fsSL https://get.docker.com | bash -s docker --mirror aliyun service docker start 1 ...

  3. Winform 使用WebView2 开发现代应用

    使用 WebView2 开发现代应用 WebView2 是 Microsoft 提供的一种嵌入式浏览器控件,基于 Edge (Chromium) 引擎.它允许开发者将现代 Web 技术(如 HTML. ...

  4. 反微服务架构(A Macro Services Framework)

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 反微服务架构(A Macro Services Frame ...

  5. JAVA-通过大疆TSDK的API直接获取红外图片温度信息

    一.前言 看过很多关于大疆红外图片用TSDK取温的方式,但是网上能搜到的大部分教程都是通过官方下载文件smple编译出来的程序来取温,如果这样做,虽然确实也能够实现目的,但不得不说,不但会降低运行速度 ...

  6. Llama 3.2 900亿参数视觉多模态大模型本地部署及案例展示

    Llama 3.2 900亿参数视觉多模态大模型本地部署及案例展示 本文将介绍如何在本地部署Llama 3.2 90B(900亿参数)视觉多模态大模型,并开发一些Use Case,展示其强大的视觉理解 ...

  7. 如何在 Kubernetes Pod 和您的机器之间复制文件

    在 Kubernetes Pod 中运行的容器是不需要手动交互的独立计算单元.有时您可能需要将文件复制到 Pod 的文件系统或从 Pod 的文件系统复制文件,这可能是因为您正在调试问题并希望存档存储在 ...

  8. java - Powermock-Failed to transform class with name...ArrayIndexOutOfBoundsException: 3

    I'm trying to run a junit test. i'm using: junit 4.10,easymock 3.0,powermock-core 1.4.10 (uses javas ...

  9. java判断反射的Field是不是用final修饰

    今天调用Field.set方法报错: Caused by: java.lang.IllegalAccessException: Can not set static final long field ...

  10. 记录vue和element-ui导出表格到excell

    1.安装插件 npm install 'file-saver' npm install 'xlsx' 2.引入插件 import FileSaver from 'file-saver' import ...