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

Unity UWP蓝牙多设备通信时RequestAccessAsync抛出ObjectDisposedException

Unity蓝牙多设备通信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同步上下文相关调用栈)
--- 从之前抛出异常的位置开始的堆栈跟踪结束 ---

问题分析

  1. 递归重试引发对象生命周期混乱:catch块中直接递归调用CommunicateWithDevice,旧异步任务未完成就启动新任务,旧任务中的service对象可能被GC回收或释放,新任务访问时触发异常。
  2. GATT对象未被强引用持有:UWP的GATT服务/特征对象若没有强引用,在异步操作间隙易被GC回收,多设备并发时GC压力更大,触发Disposed异常概率更高。
  3. 并发访问无控制:多设备任务同时执行时,共享资源未加锁,可能导致对象被意外释放。

解决方案

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 08:09:23