多线程BLE地址获取函数cv.notify_all()触发访问违规错误排查
错误根因分析
- 核心问题:函数栈变量生命周期和回调生命周期不匹配
你代码中用到的std::mutex m、std::condition_variable cv、addr、stopCalled、advWatcher都是GetFirstAdvertisingBLEAddr函数的栈上局部变量。两个事件回调(Received、Stopped)都是按引用捕获这些栈变量,而WinRT的BLE事件回调是异步执行的:当cv.wait条件满足返回后,函数会立刻开始销毁栈上的局部变量,此时如果回调还在执行(甚至还没开始调度执行),访问已经被销毁的cv、mutex等对象就会触发"访问违规"错误,你报错的cv.notify_all()行就是刚好访问到了已析构的cv对象。 - 其他多线程问题
- 共享变量存在数据竞争:
Received回调中写addr时没有加锁,和wait谓词中读addr的操作构成数据竞争,属于C++未定义行为。 - 回调移除时机过晚:你在
wait返回后才移除事件回调,此时已经有处于调度中、未执行的回调无法被拦截,依然会访问已销毁的栈变量。
- 共享变量存在数据竞争:
修复方案
- 把所有跨线程共享的状态封装到堆上,用
std::shared_ptr持有,让回调的生命周期和共享状态的生命周期绑定,避免提前销毁:
#include <memory> #include <mutex> #include <condition_variable> using namespace winrt::Windows::Devices::Bluetooth; using namespace winrt::Windows::Devices::Bluetooth::Advertisement; struct BleWatchState { std::mutex mtx; std::condition_variable cv; uint64_t foundAddr = 0; bool isStopped = false; }; uint64_t GetFirstAdvertisingBLEAddr() { auto state = std::make_shared<BleWatchState>(); BluetoothLEAdvertisementWatcher advWatcher; auto recvToken = advWatcher.Received([state, advWatcher](BluetoothLEAdvertisementWatcher const&, BluetoothLEAdvertisementReceivedEventArgs const& eventArgs) { ShowAdvertisement(eventArgs); // 加锁访问共享变量,避免重复处理 { std::unique_lock l(state->mtx); if (state->foundAddr != 0) return; state->foundAddr = eventArgs.BluetoothAddress(); } advWatcher.Stop(); std::unique_lock l(state->mtx); state->cv.notify_all(); }); auto stoppedToken = advWatcher.Stopped([state](BluetoothLEAdvertisementWatcher const&, BluetoothLEAdvertisementWatcherStoppedEventArgs const&) { std::unique_lock l(state->mtx); state->isStopped = true; state->cv.notify_all(); }); uint64_t result = 0; { std::unique_lock l(state->mtx); advWatcher.Start(); state->cv.wait(l, [&] { return state->foundAddr != 0 && state->isStopped && (advWatcher.Status() == BluetoothLEAdvertisementWatcherStatus::Aborted || advWatcher.Status() == BluetoothLEAdvertisementWatcherStatus::Stopped); }); result = state->foundAddr; } // 先停止再移除回调 if (advWatcher.Status() == BluetoothLEAdvertisementWatcherStatus::Started) { advWatcher.Stop(); } advWatcher.Received(recvToken); advWatcher.Stopped(stoppedToken); return result; }
- 额外优化点:如果需要处理超时场景,可以把
cv.wait换成cv.wait_for,避免设备未扫描到时函数永久阻塞。
内容的提问来源于stack exchange,提问作者xikov
相关产品推荐
相关产品推荐

