QFutureWatcher 收不到 finished 信号-QFutureWatcher 与对象生命周期

Qt 异步任务与对象生命周期的"隐形地雷"

QFutureWatcher 与对象生命周期

窗口关闭时 QFutureWatcher 收不到 finished 信号的两种根治方案

完整代码

cpp 复制代码
class SubWidget : public QMainWindow
{
	Q_OBJECT
public:
	SubWidget(QWidget *parent = nullptr);
	~SubWidget();

protected:
	void closeEvent(QCloseEvent* event) override;
private slots:
	void onQFutureWatcherFinished();
private:
	QFutureWatcher<bool>* createWatcher();
private:
	Ui::SubWidgetClass ui;
	QPointer<QFutureWatcher<bool>> m_pWatcher;
};
cpp 复制代码
#include <QDebug>
#include <QFuture>
#include <QThread>
#include <QtConcurrent/QtConcurrent>
SubWidget::SubWidget(QWidget *parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);

}

SubWidget::~SubWidget()
{
	/*
	如果testAttribute(Qt::WA_DeleteOnClose) == true,
	就等同于在析构函数中调用了m_pWatcher = createWatcher();
	*/
	qDebug() << "SubWidget::~SubWidget: destructor called";
	int debug = 0;
}

void SubWidget::closeEvent(QCloseEvent* event)
{
	QMainWindow::closeEvent(event);
	createWatcher();
	/*
	如果testAttribute(Qt::WA_DeleteOnClose) == true,
	就等同于在析构函数中调用了m_pWatcher = createWatcher();	
	*/
	// 
#if 解决方案一
	m_pWatcher = createWatcher();
	if (m_pWatcher != nullptr && m_pWatcher->isRunning())
	{
		qDebug() << "SubWidget::closeEvent: m_pWatcher is running, waiting for it to finish";
		m_pWatcher->waitForFinished();
		qDebug() << "SubWidget::closeEvent: waiting for it to finished";

		int debug = 0;
	}
#endif
}

void SubWidget::onQFutureWatcherFinished()
{
	qDebug() << "QObject::sender():" << QObject::sender()->objectName();
	auto pWatcher = dynamic_cast<QFutureWatcher<bool> *>(sender());
	if (pWatcher == nullptr)
	{
		qDebug() << "onQFutureWatcherFinished: pWatcher is nullptr";
		return;
	}
	qDebug() << "onQFutureWatcherFinished: pWatcher is not nullptr";

	pWatcher->deleteLater();
}

QFutureWatcher<bool>* SubWidget::createWatcher()
{
	QFutureWatcher<bool>* pWatcher = new QFutureWatcher<bool>;

	pWatcher->setObjectName(QString("pWatcher_SubWidget_%1").arg(this->testAttribute(Qt::WA_DeleteOnClose)));
	connect(pWatcher, &QFutureWatcher<bool>::finished, this, &SubWidget::onQFutureWatcherFinished);
	//解决方案二
#if 1
	connect(pWatcher, &QFutureWatcher<bool>::finished, [pWatcher]() {
		qDebug() << "QFutureWatcher finished";
	
		if (pWatcher == nullptr)
		{
			qDebug() << "QFutureWatcher is nullptr";
			return;
		}
		qDebug() << "QObject::sender():" << pWatcher->objectName();

		auto re = pWatcher->result();
		int debug = 0;
		});
#endif
	pWatcher->setFuture(QtConcurrent::run([=]() -> bool {
		qDebug() << "Running in a separate thread";
		QThread::sleep(3); 
		return true; 
	}));

	return	pWatcher;
}

使用

cpp 复制代码
	auto pSubWidget = new SubWidget(this);
	pSubWidget->setAttribute(Qt::WA_DeleteOnClose, true);
	pSubWidget->show();

现象回放

1.为什么在 closeEvent 里启动任务却永远等不到 finished?

  • Qt::WA_DeleteOnClose 为 true,析构后槽不被调用。

尝试在窗口关闭时做异步清理:

cpp 复制代码
void SubWidget::closeEvent(QCloseEvent* e)
{
    QMainWindow::closeEvent(e);
    createWatcher();          // 启动 QtConcurrent::run
}

运行结果:

  • 控制台只打印
cpp 复制代码
  Running in a separate thread
  SubWidget::~SubWidget: destructor called
  • 永远收不到
cpp 复制代码
  onQFutureWatcherFinished ...

根本原因:

  1. createWatcher() 产生的 QFutureWatcher 没有父对象,也没有任何智能指针托管。
  2. 当 closeEvent 返回后,事件循环继续,SubWidget 可能立即被 deleteLater 析构。
  3. 如果 SubWidget 先析构,而线程 3 秒后才发射 finished,信号会投递到一块已释放的内存。
  4. SubWidget 析构后,所有以 this 为接收者的 connect 自动断开;finished 信号再也找不到对象,于是消失在空气中。

2.方案总览

方案 思路 是否阻塞 UI 是否安全 适用场景
方案一:阻塞等待 在 closeEvent 里 waitForFinished() 是 安全(但卡 UI) 非常简单的清理工作,用户可接受假死
方案二:异步自毁 让 QFutureWatcher 自己 deleteLater(),不再依赖 this 否 安全 真正异步,用户体验好

3.方案一:阻塞等待(waitForFinished)

3.1 代码

cpp 复制代码
void SubWidget::closeEvent(QCloseEvent* e)
{
    QMainWindow::closeEvent(e);

    m_pWatcher = createWatcher();           // createWatcher 见题面
    if (m_pWatcher && m_pWatcher->isRunning())
    {
        qDebug() << "waiting...";
        m_pWatcher->waitForFinished();      // 阻塞
        qDebug() << "wait done";
    }
}

3.2 要点

  1. waitForFinished() 会阻塞当前线程(通常是 GUI 线程),界面会卡住 3 秒。
  2. 因为阻塞期间 SubWidget 对象仍在,所以槽函数可以正常调用,不会出现野指针。
  3. 不需要额外的 deleteLater(),函数结束后 m_pWatcher 作为普通局部变量被销毁即可。

3.3 优缺点

  • ✅ 实现简单、无生命周期坑
  • ❌ UI 假死;如果任务很长,体验极差

4.方案二:异步自毁(Lambda + deleteLater)

4.1 核心思想

  • 把 QFutureWatcher 的生命周期与 SubWidget 解耦。
  • 用捕获列表 [=] 把 pWatcher 拷进 lambda,不再使用 this。
  • 任务结束时自己 deleteLater(),彻底避免野指针。

4.2 代码(简化后)

cpp 复制代码
QFutureWatcher<bool>* SubWidget::createWatcher()
{
    auto* watcher = new QFutureWatcher<bool>;   // 无父对象
    watcher->setObjectName("watcher_async");

    // 关键:不连接到 this,而是连接到 lambda
    connect(watcher, &QFutureWatcher<bool>::finished,
            [watcher] {
                qDebug() << "finished, result =" << watcher->result();
                watcher->deleteLater();
            });

    watcher->setFuture(QtConcurrent::run([] {
        qDebug() << "Running in a separate thread";
        QThread::sleep(3);
        return true;
    }));

    return watcher;   // 调用者可立即返回,无需等待
}

调用方:

cpp 复制代码
void SubWidget::closeEvent(QCloseEvent* e)
{
    QMainWindow::closeEvent(e);
    createWatcher();   // 立即返回,不阻塞
}

4.3 要点

  1. 不依赖 this:即使 SubWidget 马上析构,lambda 仍持有 watcher 的拷贝,不会变成悬垂连接。
  2. 自动销毁:deleteLater() 把销毁动作排入事件循环,线程结束后回到 GUI 线程时安全析构。
  3. 无内存泄漏:只要 finished 信号被发射一次,就必然触发 deleteLater()。
  4. UI 不卡顿:完全符合"异步任务"初衷。

  1. 两种方案对比总结
维度 方案一:阻塞等待 方案二:异步自毁
是否卡 UI 是 否
代码复杂度 低 中
生命周期安全 高 高(需遵循不访问 this)
适用场景 快速退出、简单清理 耗时任务、优雅关闭
Qt 官方推荐 ❌ ✅

  1. 常见踩坑清单

  2. 在析构函数里 new 一个对象却不 delete

    → 内存泄漏或野指针。

  3. 把 finished 连接到 this 槽,但对象先走

    → 信号投递到僵尸对象。

  4. 在 destroyed 信号里再 deleteLater(sender())

    → 重复释放,直接崩溃。

  5. 以为 createWatcher() 返回的裸指针会自动管理

    → Qt 只有 QObject 树才会自动析构,普通裸指针不会。


  1. 一句话结论
  • 短任务、可接受假死 → 用方案一 waitForFinished(),简单粗暴。
  • 长任务、要求流畅 → 用方案二 Lambda + deleteLater,彻底摆脱对象生命周期噩梦。
相关推荐
慧都小项3 天前
当边缘AI上产线:QtitanDocking 如何让工业 HMI 实现多视图协同
人工智能·qt·ui·边缘计算·qt6.3
Quz3 天前
QML SwipeDelegate 滑动委托
qt
Quz3 天前
QML 列表中的四种委托:ItemDelegate、CheckDelegate、RadioDelegate、SwitchDelegate
qt
实心儿儿3 天前
Qt — 显示类控件
qt
江湖人称菠萝包4 天前
【Qt】《Qt 5.9 C++开发指南》笔记-Chapter9-Qt Charts
笔记·qt·qt5
扶尔魔ocy4 天前
【QT android】环境安装及第一个QT项目
qt·andriod开发
江湖人称菠萝包4 天前
【Qt】《Qt 5.9 C++开发指南》笔记-Chapter10-Data Visualization
笔记·qt·qt5
Cx330❀4 天前
Qt 常用控件属性与底层机制详解:从窗口半透明、浮点数陷阱到 QSS 与焦点策略
qt·ui·图形渲染
Quz4 天前
QML DelegateChooser:多条件组合与按列选择委托
qt
Quz4 天前
QML DelegateChooser:按角色选择委托与按索引选择委托
qt