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
相关产品推荐
相关产品推荐

