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

UWP开发BLE外设:WriteRequested与ReadRequested事件多次迭代后无响应

UWP BLE外设文件传输:多次读写后Write/ReadRequested事件停止触发

我基于UWP开发BLE外设,需要向移动客户端传输文件。实现逻辑为:服务端将文件分块,客户端通过WriteAsync请求触发分块处理,再通过ReadAsync请求获取处理后的响应。客户端流程是先获取分块数量,再循环对应次数,每次发送WriteAsync并通过ReadAsync接收响应。但目前遇到问题:经过几次迭代后,WriteRequested与ReadRequested事件不再被触发。

问题代码

private GattLocalCharacteristic fileListCharacteristic;
private string exceptionString = "Exception: ";
private Guid characteristicGuid = new Guid("xxxxxx");
private Guid serviceGuid = new Guid("xxxxxx");
private ResponseModel responseModel;
private List<byte[]> data;  
private byte[] chunkBuffer;  

// 创建GATT服务提供者
GattServiceProviderResult serviceProviderResult = await GattServiceProvider.CreateAsync(serviceGuid);
if (serviceProviderResult.Error == BluetoothError.Success)
{
    GattServiceProvider serviceProvider = serviceProviderResult.ServiceProvider;

    // 创建GATT特征
    GattLocalCharacteristicParameters characteristicParameters = new GattLocalCharacteristicParameters
    {
        CharacteristicProperties = GattCharacteristicProperties.Read | GattCharacteristicProperties.WriteWithoutResponse | GattCharacteristicProperties.Notify,
        UserDescription = "File List Characteristic"
    };
    GattLocalCharacteristicResult characteristicResult = await serviceProvider.Service.CreateCharacteristicAsync(characteristicGuid, characteristicParameters);

    if (characteristicResult.Error == BluetoothError.Success)
    {
        fileListCharacteristic = characteristicResult.Characteristic;
        fileListCharacteristic.ReadRequested += FileListCharacteristicReadRequested;
        fileListCharacteristic.WriteRequested += FileListCharacteristicWriteRequested;

        // 开始广播GATT服务
        serviceProvider.StartAdvertising();
    }
    else
    {
        Console.WriteLine("Error creating GATT characteristic: " + characteristicResult.Error);
    }
}
else
{
    Console.WriteLine("Error creating GATT service: " + serviceProviderResult.Error);
}

private async void FileListCharacteristicWriteRequested(GattLocalCharacteristic sender, GattWriteRequestedEventArgs args)
{
    var deferral = args.GetDeferral();
    GattWriteRequest request = await args.GetRequestAsync();
    DataReader reader = DataReader.FromBuffer(request.Value);
    byte[] dataBytes = new byte[reader.UnconsumedBufferLength];
    reader.ReadBytes(dataBytes);
    string dataString = Encoding.UTF8.GetString(dataBytes);

    if (string.IsNullOrWhiteSpace(dataString))
        return;
    responseModel = JsonConvert.DeserializeObject<ResponseModel>(dataString);
    if (request.Option == GattWriteOption.WriteWithResponse)
    {
        request.Respond();
    }           
    deferral.Complete();            
}

private async void FileListCharacteristicReadRequested(GattLocalCharacteristic sender, GattReadRequestedEventArgs args)
{
    var deferral = args.GetDeferral();           
    string responseString = "OK";
    switch (responseModel.ResponseCode)
    {
        case 1:
            List<FileInfoModel> fileList = await GetFileList();
            responseString = SerializeFileList(fileList);
            break;

        case 2:
            byte[] fileBuffer = await GetFileContent(responseModel.ResponseMessage);
            int bufferChunkSize = 508;
            int ChunksCount = (int)Math.Ceiling((double)fileBuffer.Length / bufferChunkSize);
            data = DivideByteArray(fileBuffer,bufferChunkSize);
            ChunkInfo chunkInfo = new ChunkInfo()
            {
                FullPath = responseModel.ResponseMessage,
                ChunkCount = ChunksCount
            };
            responseString = JsonConvert.SerializeObject(chunkInfo);
            break;

        case 3:
            int currentChunk = responseModel.CurrentChunk;
            chunkBuffer = data[currentChunk];
            break;

        default:
            responseString = "OK";
            break;
    }
    IBuffer buffer = responseModel.ResponseCode == 3 ? chunkBuffer.AsBuffer() : Encoding.UTF8.GetBytes(responseString).AsBuffer();
    GattReadRequest request = await args.GetRequestAsync();
    request?.RespondWithValue(buffer);
    deferral.Complete();
}

问题排查与修复建议

  1. 全局变量线程安全问题
    你使用了responseModel、data、chunkBuffer等全局共享变量,但BLE事件回调在后台线程触发,并发读写会导致状态混乱,甚至引发未处理异常,最终导致事件订阅失效。

    • 修复:用lock块保护全局变量的访问,或者将请求状态绑定到单个请求上下文,避免共享状态。
  2. Deferral未正确完成
    在WriteRequested中,如果dataString为空直接return,未调用deferral.Complete(),会导致系统认为请求未处理,阻塞后续请求。

    • 修复:用try/finally或using确保deferral始终被完成。
  3. 未处理异常导致事件处理器失效
    ReadRequested中若responseModel未初始化(客户端先读后写),访问responseModel.ResponseCode会抛出空引用异常,未捕获的异常会让系统移除事件处理器,后续事件不再触发。

    • 修复:添加空值检查,捕获并记录异常,确保事件处理器不会崩溃。
  4. 读写请求响应不完整
    对于WriteWithResponse请求,无论处理成功与否都应返回响应;分块索引无效时,需返回BLE协议错误码,避免客户端等待超时。

修改后的关键代码示例

修复后的WriteRequested事件

private async void FileListCharacteristicWriteRequested(GattLocalCharacteristic sender, GattWriteRequestedEventArgs args)
{
    using var deferral = args.GetDeferral();
    try
    {
        GattWriteRequest request = await args.GetRequestAsync();
        DataReader reader = DataReader.FromBuffer(request.Value);
        byte[] dataBytes = new byte[reader.UnconsumedBufferLength];
        reader.ReadBytes(dataBytes);
        string dataString = Encoding.UTF8.GetString(dataBytes);

        if (!string.IsNullOrWhiteSpace(dataString))
        {
            lock (this)
            {
                responseModel = JsonConvert.DeserializeObject<ResponseModel>(dataString);
            }
        }
        if (request.Option == GattWriteOption.WriteWithResponse)
        {
            request.Respond();
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"{exceptionString}{ex.Message}");
        var request = await args.GetRequestAsync();
        if (request?.Option == GattWriteOption.WriteWithResponse)
        {
            request.RespondWithProtocolError((byte)GattProtocolError.UnlikelyError);
        }
    }
}

修复后的ReadRequested事件

private async void FileListCharacteristicReadRequested(GattLocalCharacteristic sender, GattReadRequestedEventArgs args)
{
    using var deferral = args.GetDeferral();
    try
    {
        ResponseModel localResponseModel;
        List<byte[]> localData;
        lock (this)
        {
            if (responseModel == null)
            {
                var request = await args.GetRequestAsync();
                request?.RespondWithProtocolError((byte)GattProtocolError.InvalidPdu);
                return;
            }
            localResponseModel = responseModel;
            localData = data;
        }

        string responseString = "OK";
        byte[] chunkBuffer = null;

        switch (localResponseModel.ResponseCode)
        {
            case 1:
                List<FileInfoModel> fileList = await GetFileList();
                responseString = SerializeFileList(fileList);
                break;
            case 2:
                byte[] fileBuffer = await GetFileContent(localResponseModel.ResponseMessage);
                int bufferChunkSize = 508;
                int chunksCount = (int)Math.Ceiling((double)fileBuffer.Length / bufferChunkSize);
                var dividedData = DivideByteArray(fileBuffer, bufferChunkSize);
                lock (this)
                {
                    data = dividedData;
                }
                ChunkInfo chunkInfo = new ChunkInfo()
                {
                    FullPath = localResponseModel.ResponseMessage,
                    ChunkCount = chunksCount
                };
                responseString = JsonConvert.SerializeObject(chunkInfo);
                break;
            case 3:
                int currentChunk = localResponseModel.CurrentChunk;
                if (localData != null && currentChunk >= 0 && currentChunk < localData.Count)
                {
                    chunkBuffer = localData[currentChunk];
                }
                else
                {
                    var request = await args.GetRequestAsync();
                    request?.RespondWithProtocolError((byte)GattProtocolError.InvalidOffset);
                    return;
                }
                break;
            default:
                responseString = "OK";
                break;
        }

        IBuffer buffer = localResponseModel.ResponseCode == 3 && chunkBuffer != null 
            ? chunkBuffer.AsBuffer() 
            : Encoding.UTF8.GetBytes(responseString).AsBuffer();
        
        GattReadRequest request = await args.GetRequestAsync();
        request?.RespondWithValue(buffer);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"{exceptionString}{ex.Message}");
        var request = await args.GetRequestAsync();
        request?.RespondWithProtocolError((byte)GattProtocolError.UnlikelyError);
    }
}

内容的提问来源于stack exchange,提问作者Santos

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 10:58:09