You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用CancelAfter时如何避免任务取消后调用SetResult报错

问题确认

你猜测的竞态条件确实存在:IsCanceled的读取和SetResult的调用不是原子操作,两者的执行间隙完全可能触发超时逻辑执行SetCanceled,导致SetResult抛出InvalidOperationException。

关于TaskCompletionSource的线程安全

文档中说的线程安全指的是TaskCompletionSource<T>的所有公开方法都不会出现内部状态损坏,但Set*系列方法(SetResult/SetCanceled/SetException)的语义是「必须成功设置状态」,如果TCS已经进入终态(已完成/已取消/已出错)就会直接抛异常。
而Try*系列方法(TrySetResult/TrySetCanceled/TrySetException)就是你要的原子操作版本:它会原子检查TCS当前状态,只有未进入终态时才会修改状态并返回true,否则直接返回false,不会抛出任何异常。

最优解决方案

直接把所有Set*调用改为Try*即可,完全不需要外部的if(!req.IsCanceled)判断,修改后的代码如下:

class Request<T> {
    // 注意原简化代码漏了初始化,实际使用需要创建实例
    TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
    CancellationTokenSource cts = new CancellationTokenSource();
    public int Timeout { get; }
    public bool IsCanceled => tcs.Task.IsCanceled;

    public Request(int timeout = System.Threading.Timeout.Infinite) => Timeout = timeout;

    public Task<T> StartRequestAsync() {
        // 取消回调也改用TrySetCanceled,避免和OnEvent竞争时抛异常
        cts.Token.Register(() => tcs.TrySetCanceled());
        cts.CancelAfter(Timeout);
        return tcs.Task;
    }

    public void OnEvent(T result) {
        // 原子设置结果,成功后主动停止超时,释放不必要的资源
        if (tcs.TrySetResult(result))
        {
            cts.CancelAfter(System.Threading.Timeout.Infinite);
        }
    }
}

外部调用直接简化为:

var req = new Request<int>();
req.OnEvent(5);

其他可选方案

也可以通过加锁把IsCanceled判断和SetResult包成原子操作,但这种方案完全没必要:Try*系列方法底层已经做了原子同步,性能比手动加锁更好,代码也更简洁,没有额外的维护成本。

内容的提问来源于stack exchange,提问作者Sorashi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 12:06:03