C# WinForms中无法通过按钮停止线程问题求助
问题解决:WinForms串口监控程序停止时抛出ObjectDisposedException
问题根源
你的代码核心问题出在btn_start_Click里的using var cts = _cts = new();:
using声明会在当前方法(btn_start_Click)执行完毕后自动释放CancellationTokenSource实例- 但你把这个实例赋值给了类级别的
_cts字段,点击停止按钮时,调用的是一个已经被释放的CancellationTokenSource的Cancel()方法,自然会抛出ObjectDisposedException - 另外,线程里用
Thread.Sleep(2500)会阻塞线程,即使取消信号触发,也要等sleep结束才能响应,导致停止不及时
修复方案(直接修正现有代码)
- 移除
using声明,手动管理CancellationTokenSource的生命周期 - 用
WaitHandle.WaitOne替代Thread.Sleep,让线程能即时响应取消信号 - 停止时正确释放资源
修正后的代码:
private CancellationTokenSource? _cts; // 假设你已初始化SerialPort实例 private SerialPort? _serialPort; private void btn_start_Click(object sender, EventArgs e) { _cts = new CancellationTokenSource(); try { Thread thread = new(() => serialserver(_cts.Token)); thread.Start(); } catch (Exception ex) { Invoke((MethodInvoker)(() => txtbox_log.AppendText($"启动失败: {ex.Message}{Environment.NewLine}") )); } btn_start.Enabled = false; btn_stop.Enabled = true; } public void serialserver(CancellationToken token) { Invoke((MethodInvoker)(() => { txtbox_log.AppendText("Serial Server Started"); txtbox_log.AppendText(Environment.NewLine); })); while (!token.IsCancellationRequested) { Invoke((MethodInvoker)(() => { txtbox_log.AppendText("TEST! I'm running"); txtbox_log.AppendText(Environment.NewLine); })); // 用WaitHandle响应取消,代替Thread.Sleep,实现即时停止 if (token.WaitHandle.WaitOne(2500)) { break; } // 串口数据读取逻辑 try { if (_serialPort?.IsOpen == true && _serialPort.BytesToRead > 0) { string data = _serialPort.ReadExisting(); Invoke((MethodInvoker)(() => txtbox_log.AppendText($"收到数据: {data}{Environment.NewLine}") )); } } catch (Exception ex) { Invoke((MethodInvoker)(() => txtbox_log.AppendText($"串口错误: {ex.Message}{Environment.NewLine}") )); } } Invoke((MethodInvoker)(() => { txtbox_log.AppendText("Serial Server Stopped"); txtbox_log.AppendText(Environment.NewLine); })); } private void btn_stop_Click(object sender, EventArgs e) { if (_cts != null) { _cts.Cancel(); _cts.Dispose(); _cts = null; } // 停止时关闭串口 if (_serialPort?.IsOpen == true) { _serialPort.Close(); } btn_start.Enabled = true; btn_stop.Enabled = false; }
更优实现方案(使用Task异步编程)
WinForms推荐用异步编程替代手动创建线程,代码更简洁且不阻塞UI:
private CancellationTokenSource? _cts; private SerialPort? _serialPort; private async void btn_start_Click(object sender, EventArgs e) { _cts = new CancellationTokenSource(); btn_start.Enabled = false; btn_stop.Enabled = true; try { await SerialServerAsync(_cts.Token); } catch (Exception ex) { txtbox_log.AppendText($"服务器异常: {ex.Message}{Environment.NewLine}"); } finally { btn_start.Enabled = true; btn_stop.Enabled = false; _cts?.Dispose(); _cts = null; _serialPort?.Close(); } } private async Task SerialServerAsync(CancellationToken token) { txtbox_log.AppendText("Serial Server Started" + Environment.NewLine); while (!token.IsCancellationRequested) { txtbox_log.AppendText("TEST! I'm running" + Environment.NewLine); // 异步等待,不阻塞UI线程 await Task.Delay(2500, token); // 串口读取逻辑 if (_serialPort?.IsOpen == true) { try { string data = _serialPort.ReadExisting(); if (!string.IsNullOrEmpty(data)) { txtbox_log.AppendText($"收到数据: {data}{Environment.NewLine}"); } } catch (Exception ex) { txtbox_log.AppendText($"串口读取错误: {ex.Message}{Environment.NewLine}"); } } } txtbox_log.AppendText("Serial Server Stopped" + Environment.NewLine); } private void btn_stop_Click(object sender, EventArgs e) { _cts?.Cancel(); }
额外注意事项
- 串口操作优先用
SerialPort.DataReceived事件,而非轮询,效率更高且避免线程阻塞 - 所有UI操作必须在UI线程执行,异步代码中
await会自动回到UI上下文,可直接操作控件 - 程序关闭或停止时,务必关闭串口并释放所有资源,避免串口被占用
内容的提问来源于stack exchange,提问作者Wes Price
相关产品推荐
相关产品推荐

