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,彻底摆脱对象生命周期噩梦。
相关推荐
charlie1145141919 小时前
设计自己的小传输协议 导论与概念
c++·笔记·qt·网络协议·设计·通信协议
程序员编程指南11 小时前
Qt 并行计算框架与应用
c语言·数据库·c++·qt·系统架构
程序员编程指南14 小时前
Qt 多线程调试技巧与常见问题
c语言·开发语言·c++·qt
qq_4298796716 小时前
qt webengine播放视频
开发语言·qt·音视频
赤鸢QAQ16 小时前
Qt小组件 - 9 时间卡片
开发语言·数据库·qt
galaxy_strive17 小时前
qt c++借助开源的.pro工程文件解读.pro文件中的各项配置的含义
c++·qt
小徐不徐说18 小时前
RTSP协议详解与C++实现实例
开发语言·c++·qt·通信·rtsp
鬼魅-952721 小时前
VS+Qt中使用QCustomPlot绘制曲线标签(附源码)
c++·qt
奇树谦1 天前
Qt|槽函数耗时操作阻塞主界面问题
开发语言·qt