Unity UWP蓝牙多设备通信时RequestAccessAsync抛出ObjectDisposedException
问题描述
我在Unity中开发一款读取心率等传感器数据的蓝牙应用,尝试同时连接多个蓝牙设备。当订阅超过1个通知特征(有时2个能正常工作)时,执行var accessStatus = await service.RequestAccessAsync();会抛出**ObjectDisposedException: Cannot access a disposed object.**异常。怀疑是异步任务被释放导致,想知道如何避免或是否有其他问题。
相关代码:
private async Task CommunicateWithDevice(CustomBluetoothDevice device) { #if WINDOWS_UWP if (device.bluetoothServices.Count == 0) { Debug.LogError("No services available. Did you forget to call GetDeviceServices?"); return; } foreach(var bluetoothService in device.bluetoothServices) { var service = bluetoothService.Value.gattService; if ((bluetoothService.Key.Equals(GattGuids.POWER_SERVICE) || bluetoothService.Key.Equals(GattGuids.CSC_SERVICE)) && !userSelectedDeviceCategories.Contains(bluetoothService.Key)) { continue; } var accessStatus = await service.RequestAccessAsync(); Debug.Log("service: " + service.Uuid + " | accessStatus: " + accessStatus); if (accessStatus == DeviceAccessStatus.Allowed) { try { var characteristicsResult = await service.GetCharacteristicsAsync(BluetoothCacheMode.Uncached); if (characteristicsResult.Status == GattCommunicationStatus.Success) { foreach(var characteristic in characteristicsResult.Characteristics) { GattCharacteristicProperties properties = characteristic.CharacteristicProperties; if (properties.HasFlag(GattCharacteristicProperties.Read)) { SaveReadCharacteristic(device, characteristic); } if (properties.HasFlag(GattCharacteristicProperties.Write)) { await WriteToCharacteristic(device, characteristic, true); } if (properties.HasFlag(GattCharacteristicProperties.Notify)) { await NotifyOnCharacteristic(device, characteristic); } } } } catch(Exception e) { Debug.Log("CommunicateWithDevice catch: " + e); await CommunicateWithDevice(device); } } } #endif }
异常堆栈:
ObjectDisposedException: Cannot access a disposed object.
在 BluetoothController.CommunicateWithDevice (CustomBluetoothDevice device) [0x00165] in D:\GitHub\BleApp\Assets\Scripts\Bluetooth\BluetoothController.cs:413
在 System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine] (TStateMachine& stateMachine) [0x00000] in <00000000000000000000000000000000>:0
在 BluetoothController.CommunicateWithDevice (CustomBluetoothDevice device) [0x00000] in <00000000000000000000000000000000>:0
在 BluetoothController.CommunicateWithDevice (CustomBluetoothDevice device) [0x004e3] in D:\GitHub\BleApp\Assets\Scripts\Bluetooth\BluetoothController.cs:444
...(省略重复系统调用栈)
在 BluetoothController.NotifyOnCharacteristic (CustomBluetoothDevice device, Windows.Devices.Bluetooth.GenericAttributeProfile.GattCharacteristic characteristic) [0x0013c] in D:\GitHub\BleApp\Assets\Scripts\Bluetooth\BluetoothController.cs:490
...(省略Unity同步上下文相关调用栈)
--- 从之前抛出异常的位置开始的堆栈跟踪结束 ---
问题分析
- 递归重试引发对象生命周期混乱:catch块中直接递归调用
CommunicateWithDevice,旧异步任务未完成就启动新任务,旧任务中的service对象可能被GC回收或释放,新任务访问时触发异常。 - GATT对象未被强引用持有:UWP的GATT服务/特征对象若没有强引用,在异步操作间隙易被GC回收,多设备并发时GC压力更大,触发Disposed异常概率更高。
- 并发访问无控制:多设备任务同时执行时,共享资源未加锁,可能导致对象被意外释放。
解决方案
1. 替换递归重试为循环重试
递归会导致任务栈无限增长,改用循环重试更安全,同时限制重试次数:
private async Task CommunicateWithDevice(CustomBluetoothDevice device) { #if WINDOWS_UWP int maxRetries = 3; while (maxRetries > 0) { if (device.bluetoothServices.Count == 0) { Debug.LogError("No services available. Did you forget to call GetDeviceServices?"); return; } bool taskFailed = false; foreach (var bluetoothService in device.bluetoothServices) { var service = bluetoothService.Value.gattService; if ((bluetoothService.Key.Equals(GattGuids.POWER_SERVICE) || bluetoothService.Key.Equals(GattGuids.CSC_SERVICE)) && !userSelectedDeviceCategories.Contains(bluetoothService.Key)) { continue; } // 提前检查服务是否已释放 if (service == null || !service.Dispatcher.HasThreadAccess) { Debug.LogWarning($"Service {service?.Uuid} is disposed or inaccessible"); taskFailed = true; break; } var accessStatus = await service.RequestAccessAsync(); Debug.Log("service: " + service.Uuid + " | accessStatus: " + accessStatus); if (accessStatus == DeviceAccessStatus.Allowed) { try { var characteristicsResult = await service.GetCharacteristicsAsync(BluetoothCacheMode.Uncached); if (characteristicsResult.Status == GattCommunicationStatus.Success) { foreach (var characteristic in characteristicsResult.Characteristics) { if (characteristic == null) continue; GattCharacteristicProperties properties = characteristic.CharacteristicProperties; if (properties.HasFlag(GattCharacteristicProperties.Read)) { SaveReadCharacteristic(device, characteristic); } if (properties.HasFlag(GattCharacteristicProperties.Write)) { await WriteToCharacteristic(device, characteristic, true); } if (properties.HasFlag(GattCharacteristicProperties.Notify)) { await NotifyOnCharacteristic(device, characteristic); } } } } catch (Exception e) { Debug.Log("CommunicateWithDevice catch: " + e); taskFailed = true; break; } } } if (!taskFailed) break; maxRetries--; await Task.Delay(1000); // 重试前等待1秒,避免频繁重试 } #endif }
2. 强引用持有GATT对象
在CustomBluetoothDevice中添加列表保存订阅的特征,避免被GC回收:
public class CustomBluetoothDevice { public Dictionary<Guid, GattServiceInfo> bluetoothServices; // 强引用保存已订阅的特征 public List<GattCharacteristic> SubscribedCharacteristics { get; } = new List<GattCharacteristic>(); }
在订阅通知成功后将特征加入列表:
private async Task NotifyOnCharacteristic(CustomBluetoothDevice device, GattCharacteristic characteristic) { var result = await characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify); if (result == GattCommunicationStatus.Success) { // 添加强引用 if (!device.SubscribedCharacteristics.Contains(characteristic)) { device.SubscribedCharacteristics.Add(characteristic); } // 注册通知回调逻辑... } }
3. 限制并发任务数量
用信号量控制同时处理的设备数量,避免资源耗尽:
// 限制同时处理2个设备,可根据实际调整 private readonly SemaphoreSlim _deviceSemaphore = new SemaphoreSlim(2); public async Task StartMultiDeviceCommunication(List<CustomBluetoothDevice> devices) { var tasks = devices.Select(async device => { await _deviceSemaphore.WaitAsync(); try { await CommunicateWithDevice(device); } finally { _deviceSemaphore.Release(); } }); await Task.WhenAll(tasks); }
4. 访问前检查对象状态
在使用service或characteristic前,先验证对象有效性:
// 检查服务是否可用 if (service == null || !service.Dispatcher.HasThreadAccess) { Debug.LogWarning("Service is no longer accessible"); continue; } // 检查特征是否可用 if (characteristic == null) { continue; }
内容的提问来源于stack exchange,提问作者Assistent

