C#中无法通过超时取消TaskCompletionSource返回的Task的解决方法
问题根源
你的代码无法在超时后终止RequestDataAsync,核心原因有三个:
RequestDataAsync中没有将传入的CancellationToken与TaskCompletionSource绑定,即使令牌触发取消,TaskCompletionSource也不会进入完成状态,导致方法一直等待事件触发。- 订阅的
OnResultReady事件没有在方法完成(包括取消、超时、异常)后移除,不仅会造成内存泄漏,还可能在超时后收到延迟的事件响应,干扰后续逻辑。 - 若
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
相关产品推荐
相关产品推荐

