.NET 7 WinForms应用中BluetoothLEAdvertisementWatcher触发ObjectDisposedException
.NET 7 WinForms BLE广播对话框关闭后间歇性ObjectDisposedException异常分析与解决
异常现象
在Win11系统的.NET 7 WinForms应用中,关闭显示BLE广播的模态对话框后,会间歇性抛出System.ObjectDisposedException,且广播设备的频率越高,异常触发概率越大。已将对话框设为应用生命周期内持久化(不随关闭销毁对象),但问题仍存在。
核心原因
问题本质是线程竞态条件:
BluetoothLEAdvertisementWatcher的Received事件在后台线程触发,事件处理逻辑会通过this.Invoke切换到UI线程更新控件。- 关闭对话框时,在
FormClosing中调用deviceWatcher.Stop()并移除事件,但此时系统可能还有已触发但尚未处理的Received事件在后台队列中等待执行。 - 模态对话框关闭后,即使对象本身未被Dispose,其窗口句柄会被WinForms销毁(模态对话框的生命周期特性),此时后台线程中的
this.Invoke尝试访问已失去句柄的窗体,就会抛出ObjectDisposedException。
合理解决方案
方案一:同步等待后台事件处理完成
通过线程安全的计数和同步信号量,确保在窗体关闭前,所有正在处理的Received事件都执行完毕,避免后续无效的UI调用。
修改后的对话框代码:
using System.Diagnostics; using Windows.Devices.Bluetooth.Advertisement; using System.Threading; namespace TestBle { public partial class WatcherCrashTest : Form { private BluetoothLEAdvertisementWatcher deviceWatcher; private ManualResetEventSlim _eventProcessingComplete = new ManualResetEventSlim(true); private int _activeEventCount = 0; public WatcherCrashTest() { Debug.WriteLine("Dialog constructor"); InitializeComponent(); deviceWatcher = new BluetoothLEAdvertisementWatcher(); } private void WatcherCrashTest_Shown(object sender, EventArgs e) { Debug.WriteLine("Dialog Shown"); deviceWatcher.Received += deviceWatcher_Received; deviceWatcher.Start(); } private void WatcherCrashTest_FormClosing(object sender, FormClosingEventArgs e) { Debug.WriteLine("Dialog FormClosing"); if (deviceWatcher != null) { deviceWatcher.Stop(); deviceWatcher.Received -= deviceWatcher_Received; // 等待所有正在处理的后台事件完成 _eventProcessingComplete.Wait(); } } private void deviceWatcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args) { // 线程安全标记事件开始处理 Interlocked.Increment(ref _activeEventCount); _eventProcessingComplete.Reset(); try { String BtDeviceDetails = "0x" + args.BluetoothAddress.ToString("X8") + ": " + args.Advertisement.LocalName; Debug.WriteLine(BtDeviceDetails); // 提前检查窗体状态,避免无效操作 if (!this.IsHandleCreated || this.IsDisposed) { return; } this.Invoke(new Action(() => { // 再次检查控件状态 if (!listBox1.IsDisposed) { listBox1.Items.Add(BtDeviceDetails); } })); } finally { // 线程安全标记事件处理完成 if (Interlocked.Decrement(ref _activeEventCount) == 0) { _eventProcessingComplete.Set(); } } } } }
方案二:前置检查窗体句柄状态
在调用Invoke前,先验证窗体的句柄是否存在,直接跳过无效的UI更新操作,避免触发异常。这种方案更轻量,适合不需要等待所有事件完成的场景:
修改deviceWatcher_Received方法:
private void deviceWatcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args) { String BtDeviceDetails = "0x" + args.BluetoothAddress.ToString("X8") + ": " + args.Advertisement.LocalName; Debug.WriteLine(BtDeviceDetails); // 检查窗体是否还有有效句柄 if (!this.IsHandleCreated || this.IsDisposed) { return; } // 使用BeginInvoke避免阻塞后台线程,同时在委托内再次检查控件状态 this.BeginInvoke(new Action(() => { if (!listBox1.IsDisposed) { listBox1.Items.Add(BtDeviceDetails); } })); }
临时方案的弊端说明
你之前找到的两种临时方案存在潜在问题:
Application.DoEvents():强制处理消息队列中的所有未处理消息,可能导致意外的重入逻辑(比如重复触发窗体关闭事件),违背WinForms的消息循环设计原则。- 使用主窗体
Invoke:虽然能绕过对话框的异常,但逻辑上不合理(更新对话框控件却依赖主窗体),且如果主窗体后续也被关闭,同样会触发类似异常。
内容的提问来源于stack exchange,提问作者Stephen Done
相关产品推荐
相关产品推荐

