如何通过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; }
任务停止的核心思路
- 令牌驱动统一取消:用
CancellationTokenSource作为唯一取消信号源,所有需要响应取消的逻辑(循环、等待、IO操作)都绑定这个令牌。 - 延迟迭代中插入检查点:由于yield的延迟特性,必须在每个yield节点前后检查令牌,确保迭代过程能及时响应取消。
- 明确区分取消与故障:
OperationCanceledException是正常取消的标志,单独处理它,避免将取消标记为任务故障。 - Stop只发信号不抛异常:Stop方法的职责是触发取消,异常应由任务执行线程自行抛出并处理,不要在调用Stop的线程上抛出异常。
内容的提问来源于stack exchange,提问作者Bloom Dee
相关产品推荐
相关产品推荐

