C# ValueTask和Task区别 C#什么时候应该返回ValueTask

来源:这里教程网 时间:2026-02-21 17:40:57 作者:

ValueTask 和 Task 的本质区别

Task
是引用类型,每次异步操作都会在堆上分配一个对象;
ValueTask
是结构体(
struct
),内部持有一个
Task
或直接保存结果值(如
int
),避免不必要的堆分配。

关键不是“更快”,而是“在高频、短路径、同步完成率高”的场景下减少 GC 压力。如果方法大概率异步等待(比如真正要发 HTTP 请求、读磁盘),

ValueTask
反而可能因装箱或额外判断开销略慢。

应该返回 ValueTask 的典型场景

满足以下全部条件时才考虑

ValueTask

方法是
async
的,但存在较大概率(例如 >50%)同步完成(比如缓存命中、内存队列非空、状态已就绪)
该方法被高频调用(如 ASP.NET Core 中间件、高性能网络库的读写入口) 返回类型用于 await 表达式,且调用方不重复 await 同一个实例(
ValueTask
不可重复 await,会抛
InvalidOperationException
不需将返回值存储为字段、不传给其他方法作参数(除非转成
Task
,如调用
.AsTask()

不该用 ValueTask 的常见错误

这些做法会抵消甚至逆转收益:

对纯异步 I/O 方法(如
Stream.ReadAsync
底层必然调度)强行包装成
ValueTask
—— 多余的 struct 开销 + 无实际堆节省
ValueTask
赋值给字段或属性(如
private ValueTask _init;
),后续多次 await —— 第二次 await 就崩溃
在泛型约束中要求
T : class
却返回
ValueTask<t></t>
,导致值类型实参编译失败
调用方用了
.Result
.Wait()
——
ValueTask
不支持同步阻塞,会抛异常

如何安全地暴露 ValueTask API

如果你是库作者,对外提供

ValueTask
方法,务必在文档/注释里明确两点:

该方法是否保证可 await 一次(是),以及同步完成的大致条件(如“当缓冲区有数据时同步返回”) 若用户需要多次消费或跨 await 边界使用,请显式调用
.AsTask()
避免在 public API 中返回
ValueTask<void></void>
—— 它没有明显优势,且容易让使用者误以为可重复 await

另外,.NET 6+ 的

IAsyncEnumerable<t></t>
内部大量用
ValueTask
,但那是运行时深度优化的结果,普通业务代码不必模仿。

最常被忽略的一点:

ValueTask
的性能收益高度依赖 JIT 和运行时版本,.NET Core 3.0 之前它几乎没意义;现在也只在特定热点路径上有价值,别把它当成“Task 的升级版”来全局替换。

相关推荐