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 是否安全 适用场景
方案一:阻塞等待 closeEventwaitForFinished() 安全(但卡 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,彻底摆脱对象生命周期噩梦。
相关推荐
用户805533698034 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner4 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz9 天前
QML Hello World 入门示例
qt
xcyxiner12 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner13 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner13 天前
DicomViewer (添加模型类)3
qt
xcyxiner14 天前
DicomViewer (目录调整) 2
qt
xcyxiner14 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能16 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
森G16 天前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt