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

Xamarin.Android蓝牙接收数据时Stream.Read阻塞无返回问题求助

解决Xamarin.Android蓝牙接收时Stream.Read阻塞的问题

首先得明确:蓝牙的InputStream本身就是阻塞式的,当没有数据可读时,Read/ReadAsync方法会一直等待,直到有数据或者连接断开,这是它的设计特性,不是你的代码“出错”了,只是你需要调整处理逻辑来避免无限阻塞。

先看你代码里的几个小问题,这些会导致即使有数据也可能处理错误:

  • Encoding.ASCII.GetChars(buffer)会把整个30字节的buffer转成char数组,但你实际只读取了read个字节,应该只取前read个:Encoding.ASCII.GetChars(buffer, 0, read)
  • result[0].Equals("\0")是把char和字符串比较,应该用字符常量'\0'

接下来针对阻塞问题,给你几个可行的解决方案:

方案1:读取前检查是否有数据可用

利用InputStream.Available()方法,它会返回当前可立即读取的字节数,大于0时再执行读取操作,这样就不会阻塞。你可以配合一个循环和短暂延迟来轮询:

private Stream _inputStream;
public string ReceiveData()
{
    byte[] buffer = new byte[30];
    try
    {
        // 轮询检查,最多等待3秒(可调整)
        int waitCount = 0;
        while (waitCount < 30)
        {
            int available = (_inputStream as Java.IO.InputStream).Available();
            if (available > 0)
            {
                int read = _inputStream.Read(buffer, 0, Math.Min(available, buffer.Length));
                if (read <= 0)
                {
                    return null;
                }
                // 只转换实际读取的字节
                char[] result = Encoding.ASCII.GetChars(buffer, 0, read);
                if (result[0] != '\0')
                {
                    return result[0].ToString();
                }
            }
            // 没数据就等100ms再检查
            System.Threading.Thread.Sleep(100);
            waitCount++;
        }
        // 超时返回null
        return null;
    }
    catch (Exception ex)
    {
        // 这里建议添加日志,方便调试
        Console.WriteLine($"接收异常:{ex.Message}");
        return null;
    }
}

方案2:正确处理异步取消令牌

你之前的异步版本没生效,大概率是因为没正确捕获OperationCanceledException——当取消令牌触发时,ReadAsync会抛出这个异常,你需要在catch里处理它,并且返回超时结果:

public async Task<string> ReceiveData()
{
    byte[] buffer = new byte[30];
    using (var tokenSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(1500)))
    {
        try
        {
            int read = await _inputStream.ReadAsync(buffer, 0, buffer.Length, tokenSource.Token);
            if (read <= 0)
            {
                return null;
            }
            char[] result = Encoding.ASCII.GetChars(buffer, 0, read);
            return result[0] != '\0' ? result[0].ToString() : null;
        }
        catch (OperationCanceledException)
        {
            // 超时触发取消,返回null
            return null;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"接收异常:{ex.Message}");
            return null;
        }
    }
}

注意这里去掉了无限循环,因为你的逻辑是读取一个字符就返回,不需要循环;如果需要持续接收,再把循环加回来,但要确保每次ReadAsync都能被取消。

方案3:用后台线程处理持续接收

如果需要长期监听蓝牙数据,最好把接收逻辑放到单独的后台线程里,避免阻塞UI线程,同时配合取消令牌来控制停止:

private CancellationTokenSource _listenTokenSource;
private Stream _inputStream;

public void StartListening()
{
    _listenTokenSource = new CancellationTokenSource();
    Task.Run(async () =>
    {
        byte[] buffer = new byte[30];
        while (!_listenTokenSource.Token.IsCancellationRequested)
        {
            try
            {
                // 用带超时的取消令牌
                using (var readTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_listenTokenSource.Token))
                {
                    readTokenSource.CancelAfter(1500);
                    int read = await _inputStream.ReadAsync(buffer, 0, buffer.Length, readTokenSource.Token);
                    if (read > 0)
                    {
                        char[] result = Encoding.ASCII.GetChars(buffer, 0, read);
                        if (result[0] != '\0')
                        {
                            // 这里可以把数据发送到UI线程处理,比如用MainThread.BeginInvokeOnMainThread
                            MainThread.BeginInvokeOnMainThread(() =>
                            {
                                // 更新UI或者处理数据
                                Console.WriteLine($"收到数据:{result[0]}");
                            });
                        }
                    }
                }
            }
            catch (OperationCanceledException)
            {
                // 超时,继续循环监听
                continue;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"监听异常:{ex.Message}");
                break;
            }
        }
    }, _listenTokenSource.Token);
}

// 停止监听
public void StopListening()
{
    _listenTokenSource?.Cancel();
    _listenTokenSource?.Dispose();
}

最后提醒一下:蓝牙操作一定要在后台线程执行,不要在UI线程调用,否则会导致UI卡顿甚至ANR(应用无响应)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:09:01