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<int>
{
private int _result;
private short _version;
private Action<object?>? _continuation;
private object? _state;
public int GetResult(short token)
{
if (token != _version) throw new InvalidOperationException("Invalid token");
return _result;
}
public ValueTaskSourceStatus GetStatus(short token) =>
token == _version ? ValueTaskSourceStatus.Succeeded : ValueTaskSourceStatus.Pending;
public void OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
{
_continuation = continuation;
_state = state;
}
public void SetResult(int result)
{
_result = result;
_version++;
_continuation?.Invoke(_state);
}
public void SetException(Exception error) => throw new NotSupportedException();
public short Version => _version;
}
用法示例:
public static ValueTask<int> DelayedInt() {
var source = new SimpleIntSource();
_ = Task.Run(() => {
Thread.Sleep(100);
source.SetResult(42);
});
return new ValueTask<int>(source);
}
为什么你不该在业务代码里实现 IValueTaskSource
真正棘手的地方不在接口签名,而在运行时契约:
ValueTask可能被多次 await(只要没调用
GetResult),但你的
IValueTaskSource实例通常只能完成一次;重复 await 同一个已完成的
ValueTask会传入旧
token,导致
GetResult校验失败 没有内置取消支持,要加
CancellationToken就得自己维护
OperationCanceledException路径和取消注册逻辑 无法与
async/await状态机自动集成:你不能在
async方法里
return new ValueTask<t>(mySource)</t>并指望编译器帮你生成正确状态机 —— 它只认
Task或
ValueTask构造函数中传入的
IValueTaskSource所有 .NET 内建 API(如
Stream.ReadAsync)返回的
ValueTask都封装了高度优化、经过压测的
IValueTaskSource实现(如
ReadValueTaskSource),它们复用缓冲区、跳过调度、内联回调 —— 自己写的几乎不可能比它们更优
除非你在写类似
PipeReader.ReadAsync这种每秒百万级调用的基础组件,否则直接用
Task.FromResult、
Task.Delay或标准
async方法更安全、更易维护。
