ValueTask 不是轻量级 Task,而是可选异步结果容器,其背后要么是 Task,要么是 IValueTaskSource 实现;后者仅用于高性能库底层,需手动控制状态机,业务代码不应自行实现。

ValueTask 不是“轻量级 Task”,而是可选的异步结果容器
直接说结论:ValueTask 本身不是 awaitable 的“实现”,它只是一个包装器,背后要么持有一个 Task,要么持有一个实现了 IValueTaskSource 的对象。只有当你要**完全绕过 Task 分配、手动控制异步状态机流转**时,才需要实现 IValueTaskSource —— 这不是日常开发该碰的东西,而是为高性能库(如 System.IO.Pipelines、Microsoft.Extensions.Caching)底层服务的机制。
IValueTaskSource 是什么:一个极简但危险的异步状态契约
IValueTaskSource 是一个仅含 5 个成员的接口,它把 await 行为拆解成纯方法调用:谁来决定是否已完成、如何获取结果、怎么注册回调、怎么触发完成。它不依赖 ThreadPool 或 SynchronizationContext,一切由你控制。
关键点:
-
Version字段必须每次完成时递增,否则ValueTask会因版本不匹配而抛出InvalidOperationException -
GetResult(short token)必须只被调用一次,且仅在IsCompleted返回true后;若未完成就调用,行为未定义 -
OnCompleted注册的回调,必须在你调用SetResult/SetException时被同步或异步执行 —— 且必须严格匹配传入的token - 你必须自己管理线程安全:多个线程可能并发调用
OnCompleted和SetResult
手写一个最简 IValueTaskSource:只支持成功、无取消、单次使用
下面是一个仅用于演示的最小可行实现,它模拟“稍后返回一个 int”。注意:它不处理取消、不支持重用、不保证线程安全(仅作原理示意):
public sealed class SimpleIntSource : IValueTaskSource{ private int _result; private short _version; private Action
用法示例:
public static ValueTaskDelayedInt() { var source = new SimpleIntSource(); _ = Task.Run(() => { Thread.Sleep(100); source.SetResult(42); }); return new ValueTask (source); }
为什么你不该在业务代码里实现 IValueTaskSource
真正棘手的地方不在接口签名,而在运行时契约:
-
ValueTask可能被多次 await(只要没调用GetResult),但你的IValueTaskSource实例通常只能完成一次;重复 await 同一个已完成的ValueTask会传入旧token,导致GetResult校验失败 - 没有内置取消支持,要加
CancellationToken就得自己维护OperationCanceledException路径和取消注册逻辑 - 无法与
async/await状态机自动集成:你不能在async方法里return new ValueTask并指望编译器帮你生成正确状态机 —— 它只认(mySource) Task或ValueTask构造函数中传入的IValueTaskSource - 所有 .NET 内建 API(如
Stream.ReadAsync)返回的ValueTask都封装了高度优化、经过压测的IValueTaskSource实现(如ReadValueTaskSource),它们复用缓冲区、跳过调度、内联回调 —— 自己写的几乎不可能比它们更优
除非你在写类似 PipeReader.ReadAsync 这种每秒百万级调用的基础组件,否则直接用 Task.FromResult、Task.Delay 或标准 async 方法更安全、更易维护。










