如何在C#(WPF)中停止线程?为何未触发取消异常?
你当前代码无法触发OperationCanceledException的核心原因有三个:
错误的取消方式:点击Stop时仅把
source设为null,但CancellationTokenSource的取消逻辑是通过调用Cancel()方法发送信号,直接置空对象不会给已传递的CancellationToken触发取消通知,后台循环完全感知不到停止指令。异常捕获层级错误:你的
Sleep方法用Task.WaitAny(Task.Delay(..., token)),当token触发取消时,Task.Delay抛出的OperationCanceledException会被Task.WaitAny包装成AggregateException抛出,直接捕获OperationCanceledException根本抓不到。嵌套同步循环的取消隐患:虽然你把
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

