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

如何通过CancellationToken停止自定义Task并处理异常?

问题分析与解决方案

你的核心问题是Stop方法里的TokenSource.Token.ThrowIfCancellationRequested()调用逻辑错误——它会在调用Stop的线程(比如UI线程)抛出异常,而非Start创建的任务执行线程。正确的取消逻辑应该是:通过令牌通知任务线程自行终止,异常由任务线程内部捕获处理。

一、修复Stop方法的错误实现

直接移除ThrowIfCancellationRequested()调用,Cancel()方法已会向令牌发送取消信号,任务线程会在合适时机响应这个信号:

public virtual void Stop()
{
    if (Status.Value == TaskStatus.Running || Status.Value == TaskStatus.WaitingToRun)
    {
        stopRequested = true;
        TokenSource.Cancel();
        Status.Value = TaskStatus.Canceled;
    }
}

二、让Execution迭代过程响应取消

因为Execution是延迟执行的IEnumerable,必须在迭代的关键节点检查取消令牌,确保任务能及时终止。修改Execution方法,在每次循环和最终返回前添加令牌检查:

public override IEnumerable<string> Execution()
{
    for (int i = 0; i < 10; i++)
    {
        // 每次等待前检查取消,触发后会抛出OperationCanceledException
        TokenSource.Token.ThrowIfCancellationRequested();
        
        yield return WaitFor(TimeSpan.FromSeconds(10d))
            .WithDescription($"Step {i + 1}, waiting 1 second");
    }

    TokenSource.Token.ThrowIfCancellationRequested();
    yield return "Done";
}

如果你的WaitFor是自定义等待逻辑,建议直接将CancellationToken传入方法,让等待操作本身支持取消(比如使用Task.Delay(..., token)),这样取消信号能立即中断等待,而非等到等待结束后才检查令牌。

三、正确区分取消与故障异常

在Start方法的异常处理中,单独捕获OperationCanceledException,明确区分正常取消和意外故障:

public Task Start()
{
    stopRequested = false;
    Status.Value = TaskStatus.WaitingToRun;

    Task task = new Task(() =>
        {
            Status.Value = TaskStatus.Running;

            try
            {
                IEnumerable<string> executionState = Execution();
                foreach (string state in executionState)
                    WaitState.Value = state;

                IEnumerable<string> terminationState = Termination();
                foreach (string state in terminationState)
                    WaitState.Value = state;

                Status.Value = TaskStatus.RanToCompletion;
            }
            catch (OperationCanceledException ex)
            {
                // 确认是当前任务的取消令牌触发的异常
                if (ex.CancellationToken == TokenSource.Token)
                {
                    Status.Value = TaskStatus.Canceled;
                    Logger.Info($"Task with code {Code} was canceled");
                }
                else
                {
                    Status.Value = TaskStatus.Faulted;
                    Logger.Error(ex);
                }
            }
            catch (Exception ex)
            {
                Status.Value = TaskStatus.Faulted;
                Logger.Error(ex, $"Task with code {Code} faulted unexpectedly");
            }
        },
        TokenSource.Token
    );

    task.Start(Scheduler);
    return task;
}

任务停止的核心思路

  1. 令牌驱动统一取消:用CancellationTokenSource作为唯一取消信号源,所有需要响应取消的逻辑(循环、等待、IO操作)都绑定这个令牌。
  2. 延迟迭代中插入检查点:由于yield的延迟特性,必须在每个yield节点前后检查令牌,确保迭代过程能及时响应取消。
  3. 明确区分取消与故障:OperationCanceledException是正常取消的标志,单独处理它,避免将取消标记为任务故障。
  4. Stop只发信号不抛异常:Stop方法的职责是触发取消,异常应由任务执行线程自行抛出并处理,不要在调用Stop的线程上抛出异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 12:53:15