c# ValueTaskSource 的实现和自定义异步返回类型

1次阅读

valuetasksource 不是可实例化的类,而是指实现 ivaluetasksource 接口的一组类型;它支撑 valuetask 高效、无分配地完成同步/异步操作,需正确实现 5 个成员(含 Token 校验、version 递增、线程安全 oncompleted 等),并配合自定义 awaiter 才能被 await 消费。

c# ValueTaskSource 的实现和自定义异步返回类型

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>:必须安全存储 continuationstate,并在完成时调用;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 的 IsCompletedGetResultOnCompleted

也就是说:自定义类型 ≠ 自定义 IValueTaskSource,而是「自定义类型 + 自定义 awaiter + 可选的 IValueTaskSource 实现」三者配合。多数场景下,直接返回 ValueTask<t></t> 并用私有 IValueTaskSource 实现支撑,是最简洁可控的做法。

真正难的不是写完这几十行代码,而是确保所有路径(同步完成、异步完成、并发 await、异常完成、取消)都满足 ValueTask runtime 的严格契约 —— 这些细节藏在 System.Private.CoreLibValueTask 实现里,文档极少,出错时也不友好。

text=ZqhQzanResources