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

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);
}

关键改动说明:

  1. 新增StopObserving方法:统一处理任务取消和资源释放,避免重复启动轮询任务,同时确保CancellationTokenSource被正确清理
  2. 用取消令牌控制循环:替换原来的isRunning变量,线程安全且和取消逻辑强绑定,不会出现取消后循环还继续执行的情况
  3. 取消令牌传入Task.Delay:调用Cancel()时会立即终止延迟,不会等待3秒,任务能快速退出
  4. UI更新前检查取消状态:避免在任务已经取消后还触发Dispatcher操作,从根源上解决取消时的异常
  5. 精准捕获取消异常:只处理当前任务的取消异常,区分正常取消和其他异常情况
  6. 明确UI线程调度:在触发事件前手动切换到主线程,确保UI操作的线程安全性(如果你的OnServiceStatusChanged已经做了线程调度,可以去掉这层Dispatcher.Invoke)

这样修改后,调用StopObserving()(内部调用cts.Cancel())时,任务会快速终止,不会再触发Dispatcher的任务取消异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:21:14