讲师中心 微信公众号
AI工具推荐 视频效率加速

c# IValueTaskSource 的 GetStatus 和 OnCompleted

云浩大大_3101

云浩大大_3101

发布时间:2026-01-30 10:09:10

|

970人浏览过

|

来源于php中文网

原创

GetStatus 总返回 Pending 是正常行为,因它仅在 GetResult 能立即安全返回时才返回 Succeeded 或 Faulted;错误提前返回 Succeeded 会导致读取未初始化值或 NullReferenceException。

c# ivaluetasksource<t> 的 getstatus 和 oncompleted

GetStatus 为什么总返回 ValueTaskSourceStatus.Pending?

这是最常被误解的地方:GetStatus 不是“状态轮询接口”,而是供 ValueTask 内部判断是否可安全获取结果的契约方法。它必须严格反映底层状态:只有在 GetResult 已能立即返回(即已完成且未抛异常)时,才可返回 ValueTaskSourceStatus.Succeeded;若已失败,返回 ValueTaskSourceStatus.Faulted;否则一律返回 Pending。

常见错误是提前返回 Succeeded —— 比如在异步操作刚启动、结果尚未写入 _result 字段时就改状态,会导致 ValueTask.GetAwaiter().GetResult() 读到未初始化值或引发 NullReferenceException。

  • GetStatus 返回 Pending 是常态,不是 bug
  • 状态变更必须与 SetResult/SetException 的调用严格同步(通常需加锁或用 Volatile.Write)
  • 不要在 GetStatus 里做耗时检查(如轮询 IO 完成),它会被频繁调用

OnCompleted 的回调执行时机和线程约束

OnCompleted 接收一个 Action<object></object> 和一个 object 状态对象,它的唯一职责是:当操作**最终完成**(无论成功/失败)时,确保该 Action 被调用一次。它不承诺执行线程,也不保证立即执行 —— 典型实现是把回调压入 ThreadPool 或当前 SynchronizationContext。

关键点在于“最终完成”:如果操作本身是同步完成的(比如缓存命中),OnCompleted 可能根本不会被调用(因为 ValueTask 的 awaiter 会直接走 GetResult 分支);如果异步完成,则必须确保回调只触发一次,且不能漏掉。

  • 务必用 Interlocked.CompareExchange 或 volatile 标记完成状态,防止重复调用 OnCompleted 的回调
  • 不要在 OnCompleted 内阻塞或做重逻辑,它可能运行在 IOCP 线程或 UI 线程上
  • 若需调度到特定上下文(如 WinForms/WPF),应在回调内部手动 BeginInvoke,而非在 OnCompleted 里做

完整实现中容易漏掉的三个原子操作

手写 IValueTaskSource<t></t> 时,90% 的崩溃来自状态竞争。以下三处必须原子化:

Theta EdgeCloud Skill
Theta EdgeCloud Skill

Theta EdgeCloud API密钥优先的运行时框架,具备安全的命令级授权与试运行安全保障。

下载
  • 标记“已完成”的标志位(如 _completed 字段)—— 必须用 volatile 或 Interlocked
  • 写入结果字段(_result)—— 必须在标记完成前写入,且对读取端可见(Volatile.Write 或 MemoryBarrier)
  • 保存异常引用(_exception)—— 同样需内存屏障,避免重排序导致 GetResult 读到 null 异常

下面是一个最小可行的无锁结构体实现片段(省略泛型封装):

struct ManualResetValueTaskSource<T> : IValueTaskSource<T>
{
    private T _result;
    private Exception _exception;
    private volatile int _state; // 0=Pending, 1=Succeeded, 2=Faulted

    public ValueTaskSourceStatus GetStatus(short token) => 
        _state switch
        {
            1 => ValueTaskSourceStatus.Succeeded,
            2 => ValueTaskSourceStatus.Faulted,
            _ => ValueTaskSourceStatus.Pending
        };

    public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
    {
        if (_state != 0) // 已完成,直接触发
        {
            ThreadPool.UnsafeQueueUserWorkItem(continuation, state);
            return;
        }

        // 竞争设置为“正在完成”,仅一人成功
        if (Interlocked.CompareExchange(ref _state, 1, 0) == 0)
        {
            // 设置结果后才允许 GetResult 读取
            Volatile.Write(ref _result, default); // 占位,实际由 SetResult 填充
            ThreadPool.UnsafeQueueUserWorkItem(continuation, state);
        }
    }

    public T GetResult(short token) => _state switch
    {
        1 => _result,
        2 => throw _exception!,
        _ => throw new InvalidOperationException("Not completed")
    };

    public void SetResult(T result)
    {
        _result = result;
        Volatile.Write(ref _state, 1);
    }

    public void SetException(Exception ex)
    {
        _exception = ex;
        Volatile.Write(ref _state, 2);
    }
}

什么时候真该自己实现 IValueTaskSource<t></t>?

绝大多数场景不需要。.NET 6+ 的 TaskCompletionSource<t></t> 已足够高效;ValueTask 的核心价值在于避免分配,而自定义 IValueTaskSource 的收益只在高频、短生命周期、纯内存操作的场景下才明显(例如高性能网络库中的连接池等待、无锁队列的出队等待)。

如果你只是想“让方法返回 ValueTask”,直接用 async Task<t></t> + ConfigureAwait(false) 更安全;若用了 Task.FromResult,考虑换成 ValueTask<t>(value)</t> 构造函数即可。

真正需要手写的信号很明确:你正在压测发现 TaskCompletionSource<t></t> 的 GC 分配成了瓶颈,且 profiler 显示大量 Task 对象存活在 Gen0,并确认这些等待几乎从不跨线程 —— 这时才值得投入精力。

热门AI工具

更多
蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

549

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1698

2024.03.01

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

5214

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

5371

2025.07.04

c++中volatile关键字的作用
c++中volatile关键字的作用

本专题整合了c++中volatile关键字的相关内容,阅读专题下面的文章了解更多详细内容。

814

2025.10.23

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

3228

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

5289

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3869

2025.12.29

DBeaver怎么导入数据
DBeaver怎么导入数据

DBeaver数据导入专题,整理Excel、CSV、SQL、TXT等常见文件的数据导入方法,涵盖目标数据库选择、数据表创建、字段映射、数据类型设置及导入参数配置等操作,帮助用户将本地文件中的数据写入数据库,并了解批量导入、重复数据处理和导入失败等常见问题的解决方法。

0

2026.11.05

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn