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

如何在C#(WPF)中停止线程?为何未触发取消异常?

问题原因分析

你当前代码无法触发OperationCanceledException的核心原因有三个:

  1. 错误的取消方式:点击Stop时仅把source设为null,但CancellationTokenSource的取消逻辑是通过调用Cancel()方法发送信号,直接置空对象不会给已传递的CancellationToken触发取消通知,后台循环完全感知不到停止指令。

  2. 异常捕获层级错误:你的Sleep方法用Task.WaitAny(Task.Delay(..., token)),当token触发取消时,Task.Delay抛出的OperationCanceledException会被Task.WaitAny包装成AggregateException抛出,直接捕获OperationCanceledException根本抓不到。

  3. 嵌套同步循环的取消隐患:虽然你把source.Token传到了Sleep,但如果后续串口通信这类同步操作没有主动检查token状态,取消信号也无法中断这些阻塞操作。


修复方案

步骤1:修正Stop逻辑,调用Cancel()

把Stop分支的source = null改成调用Cancel(),同时处理source为空的边界情况:

else
{
    source?.Cancel();
    StartButtonText = "START";
}

步骤2:修正Sleep方法的异常传递

把Sleep方法改成直接等待延迟任务,让取消异常直接抛出,避免被WaitAny包装:

private void Sleep(int millisecondsTimeout, CancellationToken cancellationToken)
{
    Task.Delay(millisecondsTimeout, cancellationToken).GetAwaiter().GetResult();
}

如果要保留WaitAny,需要手动拆包AggregateException里的取消异常:

private static void Sleep(int millisecondsTimeout, CancellationToken cancellationToken)
{
    try
    {
        Task.WaitAny(Task.Delay(millisecondsTimeout, cancellationToken));
    }
    catch (AggregateException ex)
    {
        ex.Handle(e => e is OperationCanceledException);
        throw; // 重新抛出取消异常供上层捕获
    }
}

步骤3:在循环中主动检查取消信号(推荐)

对于串口通信这类无法被token直接中断的同步操作,需要在循环中主动检查token状态并抛出异常:

private void Loop1()
{
    for(int i=0;i<100;i++)
    {
        source.Token.ThrowIfCancellationRequested(); // 主动触发取消异常
        Loop1Value = "Loop1: " + i.ToString();
        Sleep(1000, source.Token);
        Loop2();
    }
}

步骤4:修正UI线程更新(可选)

后台线程直接设置绑定属性存在线程安全隐患,用RunOnUI确保UI操作在主线程执行:

private void Loop1()
{
    for(int i=0;i<100;i++)
    {
        source.Token.ThrowIfCancellationRequested();
        RunOnUI(() => Loop1Value = "Loop1: " + i.ToString());
        Sleep(1000, source.Token);
        Loop2();
    }
}

核心修复后代码片段
async void MainHandler()
{
    if (StartButtonText == "START")
    {
        StartButtonText = "STOP";

        try
        {
            source = new CancellationTokenSource();
            await Task.Run((Action)Loop1, source.Token); // 将token传给Task.Run,确保任务可被取消
        }
        catch (OperationCanceledException)
        {
            MessageBox.Show("Test was aborted by user");
        }
        finally
        {
            StartButtonText = "START";
            source?.Dispose(); // 释放资源
            source = null;
        }
    }
    else
    {
        source?.Cancel();
        StartButtonText = "START";
    }
}

private void Sleep(int millisecondsTimeout, CancellationToken cancellationToken)
{
    Task.Delay(millisecondsTimeout, cancellationToken).GetAwaiter().GetResult();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 20:50:27