ValueTaskSource 是什么,为什么不能直接 new
ValueTaskSource不是一个可实例化的类,而是指实现
IValueTaskSource接口的一组类型。.NET 的
ValueTask本身不持有状态,它只在构造时绑定一个
IValueTaskSource实例(或
Task)。你无法直接
new ValueTaskSource(),因为它是接口,且底层依赖特定的同步上下文、完成机制和状态管理。
自定义异步返回类型的关键,是提供一个高效、无分配、支持同步/异步混合完成的
IValueTaskSource实现 —— 常见于高性能库(如
System.IO.Pipelines或自研零分配 I/O 层)。
手写 IValueTaskSource 的最小必要成员
实现
IValueTaskSource至少要覆盖 5 个方法,其中 3 个带
short token参数(用于区分多次 await),2 个用于获取结果。漏掉任一都会导致
ValueTask行为异常(如重复完成、GetResult 报
InvalidOperationException)。
GetResult(short token):必须检查
token是否匹配当前完成时传入的 token,否则多 await 场景下会读到错误结果
OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)</object>:必须安全存储
continuation和
state,并在完成时调用;
flags决定是否需捕获上下文(
FlowExecutionContext)
Version属性:每次完成必须递增,用于 runtime 校验是否重复 await 同一个已完成实例
注意:
short token不是“用户传入的 ID”,而是
ValueTask内部生成的唯一标记,你的实现**绝不能忽略它或硬编码为 0**。
一个线程安全的简单实现示例(无锁但用 Interlocked)
以下是一个仅支持单次完成、线程安全、无内存分配的
IValueTaskSource<int></int>示例。它不处理取消、超时、多 await,但能跑通基础流程:
public sealed class SimpleIntSource : IValueTaskSource<int>
{
private volatile int _state; // 0=not completed, 1=completed
private int _result;
private Action<object> _continuation;
private object _stateObject;
private short _version;
<pre class='brush:php;toolbar:false;'>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)
{
if (Interlocked.CompareExchange(ref _state, 1, 0) == 0)
{
_continuation = continuation;
_stateObject = state;
_version = token;
}
else
{
// 已完成,立即调度 continuation(注意:这里没做 ExecutionContext 捕获)
ThreadPool.UnsafeQueueUserWorkItem(_ => continuation(state), null);
}
}
public void SetResult(int result)
{
_result = result;
var version = Interlocked.Increment(ref _version);
if (Interlocked.Exchange(ref _state, 1) == 0 && _continuation != null)
{
_continuation(_stateObject);
}
}
public short Version => _version;}
使用方式:
var source = new SimpleIntSource(); var t = new ValueTask<int>(source); // … later source.SetResult(42); var r = await t; // 得到 42
⚠️ 容易踩的坑:
– 没检查
token导致 await 多次时
GetResult返回旧值或抛异常
–
OnCompleted中未处理已完状态,导致 continuation 永远不被调用
– 忘记递增
Version,runtime 会拒绝 await(报
InvalidOperationException: Attempted to await a ValueTask with an invalid token)
自定义异步返回类型需要额外做什么
如果想让
await myCustomType成立,除了实现
IValueTaskSource<t></t>,你还得提供一个“包装器类型”并为其添加
GetAwaiter()方法 —— 因为 C# 编译器只认
GetAwaiter,不直接查
IValueTaskSource。 该包装器通常是个
struct(避免分配),内部持有一个
IValueTaskSource<t></t>引用或内联数据
GetAwaiter()返回一个实现了
INotifyCompletion的 struct(例如
ValueTaskAwaiter<t></t>的简化版) 编译器生成的
await代码最终调用的是这个 awaiter 的
IsCompleted、
GetResult和
OnCompleted
也就是说:自定义类型 ≠ 自定义
IValueTaskSource,而是「自定义类型 + 自定义 awaiter + 可选的 IValueTaskSource 实现」三者配合。多数场景下,直接返回
ValueTask<t></t>并用私有
IValueTaskSource实现支撑,是最简洁可控的做法。
真正难的不是写完这几十行代码,而是确保所有路径(同步完成、异步完成、并发 await、异常完成、取消)都满足
ValueTaskruntime 的严格契约 —— 这些细节藏在
System.Private.CoreLib的
ValueTask实现里,文档极少,出错时堆栈也不友好。
