<think>我们分析一下问题:在Python中使用了异步任务来管理蓝牙BLE操作,但在任务未完成时退出程序,即使代码中处理了future,仍然报异常。从windbg的堆栈来看,异常发生在DLL卸载过程中,具体是在BleScanner的析构函数中,尝试释放WinRT的BluetoothLEAdvertisementWatcher对象时触发了RPC相关异常(TppRaiseInvalidParameter)。
关键点:
1. 异常触发点:`BluetoothLEAdvertisementWatcher::~BluetoothLEAdvertisementWatcher` -> 调用了RPC清理,但此时RPC运行时可能已经处于不稳定状态。
2. 根本原因:在DLL卸载时(DllMain的DLL_PROCESS_DETACH),全局对象`g_manager`的析构函数被调用,进而析构`BleManager`,而`BleManager`内部包含`BleScanner`对象(`local_scanner`)。在`BleScanner`的析构中,它试图释放WinRT对象(如`BluetoothLEAdvertisementWatcher`),但此时系统资源(如RPC)可能已经部分卸载,导致访问违规。
为什么即使Python代码中处理了future(比如取消)还会出现?
因为Python中的取消操作(future.cancel())只是设置了future的状态为取消,并移除了pending map中的引用,但并没有立即停止底层的DLL操作(比如扫描)。DLL中的扫描操作可能还在运行,并且持有WinRT对象。当DLL开始卸载时,这些对象被强制销毁,而此时系统状态已经不允许进行正常的COM/RPC调用了。
解决方案:
1. 确保在DLL卸载前,所有异步操作都被完全停止,并且资源被正确释放。
2. 在Python的`close`方法中,应该先停止所有异步任务,再卸载DLL。
具体到代码:
在`BleManagerPython`的`close`方法中,目前做了:
- 设置停止标志`_stop`
- 等待泵线程`_pump`结束(最多3秒)
- 调用DLL的`BLE_Uninitialize`
- 然后取消所有未完成的future
问题在于:在调用`BLE_Uninitialize`之后才取消future,这可能导致在DLL已经卸载后,还有异步任务试图操作DLL内部资源(比如调用DLL函数)。
调整步骤:
1. 首先取消所有未完成的future(这样会触发DLL中的操作取消,比如停止扫描)。
2. 然后等待所有操作完成(或者等待一段时间让DLL内部操作停止)。
3. 再调用`BLE_Uninitialize`卸载DLL。
修改`close`方法:
```python
def close(self):
# First, set stop flag to prevent new operations
self._stop.set()
# Cancel all pending futures and remove them from the map
with self._lock:
pending = list(self._pending.values())
self._pending.clear()
for ctx in pending:
if not ctx.future.done():
ctx.future.cancel()
# Wait for the pump thread to exit (it should exit because we set _stop)
self._pump.join(timeout=5.0)
if self._pump.is_alive():
self.logger.error("Ble pump thread did not exit, force continue")
# Now uninitialize the DLL
ret = int(self._dll.BLE_Uninitialize())
if ret:
self.logger.error("Ble DLL uninitialization failed: %d", ret)
else:
self.logger.debug("Ble DLL uninitialization succeeded")
```
但是,仅仅取消future可能还不够,因为DLL内部的操作(如扫描)可能还在运行。因此,我们需要在DLL的`BLE_Uninitialize`函数中确保所有操作都已停止。
在DLL的`BLE_Uninitialize`中,应该:
- 停止`g_manager`(这会停止所有客户端和线程)
- 停止并释放`g_scanner`(如果存在)
根据之前的代码,`BLE_Uninitialize`中已经做了:
```cpp
int BLE_Uninitialize()
{
std::lock_guard<std::mutex> lock(g_lock);
logging::Log.Debug("[DLL] Starting DLL uninitialization...");
try
{
g_manager.Stop();
logging::Log.Debug("BLE manager stopped...");
if (g_scanner)
{
g_scanner->Close();
delete g_scanner;
g_scanner = nullptr;
}
else
{
logging::Log.Warn("[DLL] Scanner already closed");
}
logging::Log.Debug("Successfully uninstalled");
return 0;
}
catch (...)
{
return -1;
}
}
```
所以,问题可能是在Python的`close`中,我们取消future和卸载DLL的顺序不对。另外,还需要确保在取消future后,DLL内部的操作有足够的时间停止。
另一个重要问题:在DLL卸载时,全局对象的析构顺序可能导致问题。因为`g_manager`是全局对象,它的析构会在DLL卸载时自动发生,而我们在`BLE_Uninitialize`中已经手动调用了`g_manager.Stop()`,但是全局对象`g_manager`的析构函数还会在DLL卸载时再次被调用(因为它是全局对象,会在DLL卸载时自动析构)。这就会导致重复调用`Stop()`,而第二次调用时,内部状态可能已经无效。
因此,我们需要避免全局对象被自动析构时再次执行清理。我们可以将`g_manager`和`g_scanner`改为指针,并在`BLE_Uninitialize`中释放它们,然后置空。这样在DLL卸载时,这些指针已经是空指针,不会再次被析构。
修改DLL中的全局变量:
```cpp
// 改为指针
static BleManager* g_manager = nullptr;
static BleScanner* g_scanner = nullptr;
int BLE_Initialize()
{
if (g_manager == nullptr) {
g_manager = new BleManager();
}
// ... 其他初始化
return 0;
}
int BLE_Uninitialize()
{
std::lock_guard<std::mutex> lock(g_lock);
logging::Log.Debug("[DLL] Starting DLL uninitialization...");
try
{
if (g_manager) {
g_manager->Stop();
delete g_manager;
g_manager = nullptr;
}
if (g_scanner) {
g_scanner->Close();
delete g_scanner;
g_scanner = nullptr;
}
logging::Log.Debug("Successfully uninstalled");
return 0;
}
catch (...)
{
return -1;
}
}
```
这样修改后,全局对象不会自动析构(因为现在是指针,需要手动管理),并且在`BLE_Uninitialize`中已经释放,所以DLL卸载时不会再次调用析构。
总结:
1. 修改Python的`close`方法,先取消所有future,再卸载DLL(调用`BLE_Uninitialize`)。
2. 修改DLL,将全局对象改为指针,并在`BLE_Uninitialize`中手动释放,避免重复析构。
3. 确保在DLL的`BLE_Uninitialize`中,先停止管理器再停止扫描器。
这样应该能够解决在任务未完成时退出导致的异常。
§§相关问题§§
1. 在Python中如何确保异步任务在程序退出前被正确取消和清理?
2. 在DLL中管理全局对象时,使用指针和手动释放相比静态全局对象有哪些优势?
3. 如何避免在DLL卸载过程中因系统资源已释放而导致的访问违规?
4. 在WinRT/C++编程中,如何安全释放BluetoothLEAdvertisementWatcher等COM对象?
5. 当Python调用DLL时,如何设计DLL的初始化和反初始化接口以确保资源安全释放?
</think>
### 问题分析:异步任务与DLL卸载的竞争条件
从异常堆栈看,核心问题是 **Python异步任务与DLL卸载的时序冲突**:
```mermaid
sequenceDiagram
participant Python
participant DLL
participant WinRT
Python->>DLL: 启动扫描任务
DLL->>WinRT: 创建BluetoothLEAdvertisementWatcher
Python->>Python: 用户取消或退出
Python->>DLL: BLE_Uninitialize()
DLL->>WinRT: 销毁BluetoothLEAdvertisementWatcher
WinRT->>RPC: 发起清理请求
Note over RPC: RPC运行时已部分卸载
RPC--x DLL: 触发c000000d异常
```
### 根本原因:
1. **异步任务未完全停止**:
- Python的`close()`方法先停止事件泵,再取消future
- 但DLL中的WinRT对象仍在后台线程运行
2. **DLL卸载时序问题**:
```python
def close(self):
self._stop.set() # 1. 设置停止标志
self._pump.join(timeout=3.0) # 2. 等待事件泵停止
self._dll.BLE_Uninitialize() # 3. 卸载DLL
# 4. 之后才取消future
```
- 步骤3卸载DLL时,WinRT对象可能仍在运行
3. **RPC运行时依赖**:
- `BluetoothLEAdvertisementWatcher`依赖RPC运行时
- DLL卸载时RPC可能已被部分清理
### 终极解决方案:安全关闭协议
#### 步骤1:Python层强化关闭顺序
```python
def close(self):
# 第一步:取消所有未完成任务
with self._lock:
pending = list(self._pending.values())
self._pending.clear()
for ctx in pending:
if not ctx.future.done():
ctx.future.cancel() # 触发DLL中的取消操作
# 第二步:等待异步操作完成
await asyncio.sleep(0.5) # 给DLL处理取消的时间
# 第三步:停止事件泵
self._stop.set()
self._pump.join(timeout=5.0)
# 第四步:卸载DLL
ret = int(self._dll.BLE_Uninitialize())
if ret:
self.logger.error("Uninitialize failed: %d", ret)
```
#### 步骤2:DLL层添加异步取消支持
```cpp
// 在ble_manager.h中添加
void CancelOperation(uint32_t op_id);
// 在ble_manager.cpp中实现
void BleManager::CancelOperation(uint32_t op_id) {
std::lock_guard lock(clients_mtx_);
for (auto& [addr, client] : clients_) {
client->CancelOperation(op_id);
}
// 如果是扫描操作
if (op_id == SCAN_OP_ID_MAGIC) {
local_scanner.StopScan();
}
}
```
#### 步骤3:强化BleScanner的停止机制
```cpp
void BleScanner::StopScan() {
if (!m_active) return;
// 原子操作设置停止标志
m_active = false;
// 通知WinRT停止扫描
if (m_watcher) {
try {
m_watcher.Stop();
} catch (...) {
// 忽略所有异常,防止在卸载时崩溃
}
}
// 等待WinRT回调完成
m_stopSignal.wait_for(500ms);
}
```
### 关键修复:WinRT对象安全释放
```cpp
BleScanner::~BleScanner() {
// 先尝试正常停止
StopScan();
// 特殊释放技术避免RPC依赖
if (m_watcher) {
// 使用COM智能指针安全释放
winrt::com_ptr<IUnknown> unknown =
winrt::get_unknown(m_watcher.as<winrt::IInspectable>());
unknown.detach(); // 放弃所有权,避免正式析构
}
// 重置为nullptr
m_watcher = nullptr;
}
```
### 调试建议:诊断异步资源泄漏
#### Windbg命令:
```
0:000> !handle -a // 查看未关闭的句柄
0:000> !threads // 检查残留线程
0:000> !comstate // 检查COM对象状态
0:000> !rpcexts.getcallinfo // 检查RPC调用
```
#### 添加诊断日志:
```cpp
BleScanner::~BleScanner() {
LogDebug("~BleScanner: Active=%d, Watcher=%p",
m_active.load(), m_watcher ? true : false);
if (m_active) {
LogError("Scanner destroyed while still active!");
}
}
```
### 安全卸载时序图
```mermaid
sequenceDiagram
participant User
participant Python
participant DLL
participant WinRT
User->>Python: 调用close()
Python->>Python: 取消所有future
Python->>DLL: CancelOperation(扫描ID)
DLL->>BleScanner: StopScan()
BleScanner->>WinRT: watcher.Stop()
WinRT-->>BleScanner: 停止确认
BleScanner-->>Python: 操作完成
Python->>DLL: BLE_Uninitialize()
DLL->>BleScanner: 析构(已安全停止)
DLL-->>Python: 卸载成功
```