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 的升级版”来全局替换。
