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

如何安全销毁OBS插件中在其他线程创建的QWidget?

OBS Studio自定义源插件的Qt窗口销毁问题

背景

我正在为OBS Studio开发自定义源类型插件,OBS自带的get_properties()函数无法满足属性编辑需求,因此用自定义QWidget作为源属性主窗口,并关闭OBS默认属性窗口。

实现流程

  • 当源调用get_properties()时,若未创建GUIHandler则实例化并调用GUIHandler::show()
  • GUIHandler::show()通过QApplication::topLevelWindows()找到OBS默认属性窗口并关闭,随后创建并显示自定义窗口OBSGraphicsMainWindow(继承自QWidget)

窗口销毁的三种场景

  1. 用户点击OBSGraphicsMainWindow的关闭按钮:此场景无问题
  2. 用户移除场景中的源,且对应的OBSGraphicsMainWindow处于打开状态:现有方案存在崩溃风险
  3. 用户关闭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后调用

解决方案建议

  1. 线程安全的对象生命周期管理:

    • 不要在析构函数中阻塞等待Qt对象销毁,OBS主线程不能长时间阻塞(会导致UI卡顿假死)
    • 改用QObject::connect监听OBSGraphicsMainWindow的destroyed信号,在回调中清理源对象引用,而非主动等待
    • 给OBSGraphicsMainWindow设置Qt::WA_DeleteOnClose属性,用户关闭窗口时自动调用deleteLater(),无需手动处理
  2. 跨线程安全的销毁方式:

    • 由于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();
      }
      
    • 避免直接访问窗口成员变量,改用信号槽传递销毁通知,减少竞态条件
  3. 源对象与GUI解耦:

    • 在OBSBlueprintGraph中添加isDestroying标记,OBSGraphicsMainWindow读写源变量前先检查该标记,避免访问已销毁对象
    • 将deleteComplete回调改为信号,GUIHandler监听后再清理自身引用,替代条件变量阻塞
  4. 优化默认属性窗口处理:

    • 使用obs_properties_set_flags(properties, OBS_PROPERTIES_FLAG_NO_DEFAULT_WINDOW)阻止OBS创建默认属性窗口,比遍历关闭更高效

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 11:50:20