C#如何在含EventHandler的任务中正确处理任务取消?
解决取消轮询任务时Dispatcher抛出任务取消异常的问题
首先咱们来拆解下你现有代码里的几个核心问题:
- 用
isRunning变量控制循环,不仅线程不安全,还和CancellationToken的取消逻辑脱节,容易出现取消后循环还继续执行的情况 Task.Delay没有传入取消令牌,导致调用cts.Cancel()后,延迟不会立即终止,还会走完3秒才退出循环- 取消时可能已经触发了
OnServiceStatusChanged,但此时任务上下文已经被取消,Dispatcher在更新UI时就会抛出任务取消的异常
下面是修正后的完整实现,我会标注关键改动点:
private CancellationTokenSource _statusPollCts; public void StartObserving() { // 先停止之前的轮询任务,避免重复启动多个任务 StopObserving(); _statusPollCts = new CancellationTokenSource(); var cancellationToken = _statusPollCts.Token; Task.Run(async () => { try { // 用取消令牌的状态作为循环条件,替代线程不安全的isRunning while (!cancellationToken.IsCancellationRequested) { // 提前检查是否已取消,避免执行无用的API调用 cancellationToken.ThrowIfCancellationRequested(); var serviceStatus = serviceAPI.GetStatus(); // 确认任务未被取消后再触发UI更新,避免无效的Dispatcher操作 if (!cancellationToken.IsCancellationRequested) { // 确保UI更新在主线程执行,这里以WPF为例,WinForms可以替换成Invoke逻辑 Application.Current.Dispatcher.Invoke(() => { OnServiceStatusChanged(serviceStatus); }); } // 将取消令牌传入Task.Delay,这样调用Cancel()时会立即终止延迟 await Task.Delay(3000, cancellationToken); } } catch (OperationCanceledException ex) { // 只处理当前任务的取消异常,忽略其他来源的取消 if (ex.CancellationToken == cancellationToken) { logger.LogInformation("服务状态轮询任务已正常终止"); } else { logger.LogError(ex, "发生非预期的任务取消异常"); } } catch (Exception ex) { logger.LogError(ex, "服务状态轮询过程中出现错误"); } finally { // 确保释放CancellationTokenSource资源,避免内存泄漏 _statusPollCts?.Dispose(); _statusPollCts = null; } }, cancellationToken); } // 新增统一的停止方法,方便外部调用取消任务 public void StopObserving() { if (_statusPollCts != null) { _statusPollCts.Cancel(); _statusPollCts.Dispose(); _statusPollCts = null; } } // 事件定义示例 public event EventHandler<ServiceStatus> ServiceStatusChanged; private void OnServiceStatusChanged(ServiceStatus status) { ServiceStatusChanged?.Invoke(this, status); }
关键改动说明:
- 新增
StopObserving方法:统一处理任务取消和资源释放,避免重复启动轮询任务,同时确保CancellationTokenSource被正确清理 - 用取消令牌控制循环:替换原来的
isRunning变量,线程安全且和取消逻辑强绑定,不会出现取消后循环还继续执行的情况 - 取消令牌传入
Task.Delay:调用Cancel()时会立即终止延迟,不会等待3秒,任务能快速退出 - UI更新前检查取消状态:避免在任务已经取消后还触发Dispatcher操作,从根源上解决取消时的异常
- 精准捕获取消异常:只处理当前任务的取消异常,区分正常取消和其他异常情况
- 明确UI线程调度:在触发事件前手动切换到主线程,确保UI操作的线程安全性(如果你的
OnServiceStatusChanged已经做了线程调度,可以去掉这层Dispatcher.Invoke)
这样修改后,调用StopObserving()(内部调用cts.Cancel())时,任务会快速终止,不会再触发Dispatcher的任务取消异常。
内容的提问来源于stack exchange,提问作者user3493916
相关产品推荐
相关产品推荐

