如何安全销毁OBS插件中在其他线程创建的QWidget?
OBS Studio自定义源插件的Qt窗口销毁问题
背景
我正在为OBS Studio开发自定义源类型插件,OBS自带的get_properties()函数无法满足属性编辑需求,因此用自定义QWidget作为源属性主窗口,并关闭OBS默认属性窗口。
实现流程
- 当源调用
get_properties()时,若未创建GUIHandler则实例化并调用GUIHandler::show() GUIHandler::show()通过QApplication::topLevelWindows()找到OBS默认属性窗口并关闭,随后创建并显示自定义窗口OBSGraphicsMainWindow(继承自QWidget)
窗口销毁的三种场景
- 用户点击
OBSGraphicsMainWindow的关闭按钮:此场景无问题 - 用户移除场景中的源,且对应的
OBSGraphicsMainWindow处于打开状态:现有方案存在崩溃风险 - 用户关闭OBS Studio主窗口,且
OBSGraphicsMainWindow处于打开状态:同样存在问题
现有实现的问题
最初在GUIHandler析构函数(源销毁时触发)中销毁窗口的代码如下:
GUIHandler::~GUIHandler() { if (window != nullptr) { window->onDeletion = nullptr; window->deleteLater(); } }
deleteLater()是非线程安全的,偶尔会访问已销毁的源对象变量导致OBS崩溃- 直接用
delete window;会触发Qt断言错误:因为OBSGraphicsMainWindow和OBS主窗口不在同一线程,无法跨线程发送事件 - 调用
window->close()会导致程序永久挂起
核心问题
在不修改OBS Studio源码的前提下,从OBS主线程销毁其他线程创建的OBSGraphicsMainWindow的最佳方案是什么?是否应该用deleteLater()配合std::mutex,让线程等待Qt对象销毁后再删除源对象?
已修改的代码尝试
头文件(.h)
#pragma once #include <functional> #include <mutex> class OBSGraphicsMainWindow; class OBSBlueprintGraph; class GUIHandler { public: GUIHandler(OBSBlueprintGraph* g); ~GUIHandler(); void show() const; private: std::condition_variable cond{}; OBSGraphicsMainWindow* window = nullptr; OBSBlueprintGraph* graph; };
实现文件(.cpp)
#include "gui-graph.h" #include <iostream> #include <QApplication> #include <QScreen> #include <QWindow> #include "obs-frontend-api.h" #include "obs-graphics-main-window.h" #include "Core/obs-blueprint-graph.h" #include "Helpers/global-logger.h" GUIHandler::GUIHandler(OBSBlueprintGraph *g) : graph(g) { QWidget *parent = static_cast<QWidget *>(obs_frontend_get_main_window()); window = new OBSGraphicsMainWindow(graph, parent, Qt::Window | Qt::CustomizeWindowHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); window->deleteComplete = [this] { GDebug("[GUI] >> Main Window DESTROYED! <<"); window = nullptr; cond.notify_all(); }; } GUIHandler::~GUIHandler() { if (window != nullptr) { std::mutex mtx{}; std::unique_lock lock{mtx}; window->deleteLater(); cond.wait_for(lock, std::chrono::milliseconds(100)); // TODO 待优化:目前关闭时GUI仍在graph之后销毁 if (window != nullptr) { window->context().onDeletion.execute(); } } } void GUIHandler::show() const { // 隐藏OBS默认属性窗口,待优化:有没有更好的方式?甚至阻止属性窗口创建? const QWindowList list = QApplication::topLevelWindows(); // 反向遍历提升性能 QList<QWindow*>::const_iterator it = list.constEnd(); while(it != list.constBegin()) { --it; if((*it)->objectName() == "OBSBasicPropertiesWindow") { (*it)->close(); break; } } window->adjustSize(); // 自适应内容大小 // 窗口居中显示 QRect screenRect = QApplication::primaryScreen()->geometry(); QRect windowRect = window->geometry(); window->move((screenRect.width() - windowRect.width())/2, (screenRect.height() - windowRect.height())/2); window->show(); }
注:
std::function<void()> deleteComplete会在OBSGraphicsMainWindow销毁所有子QObject后调用
解决方案建议
线程安全的对象生命周期管理:
- 不要在析构函数中阻塞等待Qt对象销毁,OBS主线程不能长时间阻塞(会导致UI卡顿假死)
- 改用
QObject::connect监听OBSGraphicsMainWindow的destroyed信号,在回调中清理源对象引用,而非主动等待 - 给
OBSGraphicsMainWindow设置Qt::WA_DeleteOnClose属性,用户关闭窗口时自动调用deleteLater(),无需手动处理
跨线程安全的销毁方式:
- 由于
OBSGraphicsMainWindow属于非主线程,必须通过QMetaObject::invokeMethod在窗口所属线程中调用deleteLater(),确保线程安全:if (window != nullptr && window->thread() != QThread::currentThread()) { QMetaObject::invokeMethod(window, [this]() { window->deleteLater(); }, Qt::QueuedConnection); } else if (window != nullptr) { window->deleteLater(); } - 避免直接访问窗口成员变量,改用信号槽传递销毁通知,减少竞态条件
- 由于
源对象与GUI解耦:
- 在
OBSBlueprintGraph中添加isDestroying标记,OBSGraphicsMainWindow读写源变量前先检查该标记,避免访问已销毁对象 - 将
deleteComplete回调改为信号,GUIHandler监听后再清理自身引用,替代条件变量阻塞
- 在
优化默认属性窗口处理:
- 使用
obs_properties_set_flags(properties, OBS_PROPERTIES_FLAG_NO_DEFAULT_WINDOW)阻止OBS创建默认属性窗口,比遍历关闭更高效
- 使用
内容的提问来源于stack exchange,提问作者Bukibarak
相关产品推荐
相关产品推荐

