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

来源:这里教程网 时间:2026-02-21 17:42:31 作者:

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、异常完成、取消)都满足

ValueTask
runtime 的严格契约 —— 这些细节藏在
System.Private.CoreLib
ValueTask
实现里,文档极少,出错时堆栈也不友好。

相关推荐