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

C#中无法通过超时取消TaskCompletionSource返回的Task的解决方法

问题根源

你的代码无法在超时后终止RequestDataAsync,核心原因有三个:

  1. RequestDataAsync中没有将传入的CancellationToken与TaskCompletionSource绑定,即使令牌触发取消,TaskCompletionSource也不会进入完成状态,导致方法一直等待事件触发。
  2. 订阅的OnResultReady事件没有在方法完成(包括取消、超时、异常)后移除,不仅会造成内存泄漏,还可能在超时后收到延迟的事件响应,干扰后续逻辑。
  3. 若SendRequestToAcquireDataAsync未正确处理传入的取消令牌,会继续向API发送请求并等待,无法及时终止。
修复后的代码

1. 重构RequestDataAsync

private async Task<string?> RequestDataAsync(string? remoteEntity, CancellationToken cancellationToken)
{
    var taskCompletionSource = new TaskCompletionSource<string?>(TaskCreationOptions.RunContinuationsAsynchronously);
    EventHandler<string?>? resultHandler = null;
    IDisposable? cancellationRegistration = null;

    try
    {
        if (remoteEntity is not null)
        {
            var handler = GetHandler();
            
            // 定义事件处理委托
            resultHandler = (_, result) =>
            {
                taskCompletionSource.TrySetResult(result);
            };
            handler.Event.OnResultReady += resultHandler;

            // 绑定取消令牌:当令牌触发时,取消TCS并移除事件订阅
            cancellationRegistration = cancellationToken.Register(() =>
            {
                taskCompletionSource.TrySetCanceled(cancellationToken);
                if (resultHandler != null)
                {
                    handler.Event.OnResultReady -= resultHandler;
                }
            });

            // 确保SendRequestToAcquireDataAsync响应取消令牌
            await SendRequestToAcquireDataAsync(cancellationToken);
        }
        else
        {
            taskCompletionSource.SetResult(null);
        }

        // 等待TCS完成,同时响应取消令牌
        return await taskCompletionSource.Task.WaitAsync(cancellationToken);
    }
    catch (Exception ex)
    {
        taskCompletionSource.TrySetException(ex);
        throw;
    }
    finally
    {
        // 无论成功、失败还是取消,都移除事件订阅
        if (resultHandler != null && remoteEntity != null)
        {
            var handler = GetHandler();
            handler.Event.OnResultReady -= resultHandler;
        }
        // 释放取消注册
        cancellationRegistration?.Dispose();
    }
}

2. 优化RequestDataWithTimeoutAsync(简化异常处理)

public async Task<string?> RequestDataWithTimeoutAsync(string? remoteEntity, CancellationToken cancellationToken, int requestLifetimeInMilliseconds = 10000)
{
    using var lifeTimeCts = new CancellationTokenSource(requestLifetimeInMilliseconds);
    using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, lifeTimeCts.Token);

    try
    {
        return await RequestDataAsync(remoteEntity, linkedCts.Token);
    }
    catch (OperationCanceledException ex)
    {
        // 判断是外部取消还是超时
        if (cancellationToken.IsCancellationRequested)
        {
            throw new OperationCanceledException(ex.Message, ex, cancellationToken);
        }
        throw new TimeoutException("请求超时", ex);
    }
}
关键修改说明
  • 绑定取消令牌到TCS:通过cancellationToken.Register在取消时调用TrySetCanceled,确保TCS进入完成状态,终止等待。
  • 移除事件订阅:在finally块和取消回调中都移除事件处理委托,避免内存泄漏和后续意外触发。
  • 使用TrySet*方法:避免重复设置TCS状态导致的异常(比如超时后事件又触发的情况)。
  • WaitAsync(cancellationToken):双重保障,即使TCS未绑定取消令牌,等待过程也会响应取消。
  • 简化超时令牌创建:直接使用CancellationTokenSource(int)构造函数创建超时令牌,代码更简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 01:55:11