QFutureWatcher 收不到 finished 信号-QFutureWatcher 与对象生命周期
Qt 异步任务与对象生命周期的“隐形地雷”
QFutureWatcher 与对象生命周期
窗口关闭时 QFutureWatcher 收不到 finished 信号的两种根治方案
完整代码
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;
};
#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 1connect(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;});
#endifpWatcher->setFuture(QtConcurrent::run([=]() -> bool {qDebug() << "Running in a separate thread";QThread::sleep(3); return true; }));return pWatcher;
}
使用
auto pSubWidget = new SubWidget(this);pSubWidget->setAttribute(Qt::WA_DeleteOnClose, true);pSubWidget->show();
现象回放
1.为什么在 closeEvent 里启动任务却永远等不到 finished?
Qt::WA_DeleteOnClose
为 true,析构后槽不被调用。
尝试在窗口关闭时做异步清理:
void SubWidget::closeEvent(QCloseEvent* e)
{QMainWindow::closeEvent(e);createWatcher(); // 启动 QtConcurrent::run
}
运行结果:
- 控制台只打印
Running in a separate threadSubWidget::~SubWidget: destructor called
- 永远收不到
onQFutureWatcherFinished ...
根本原因:
createWatcher()
产生的QFutureWatcher
没有父对象,也没有任何智能指针托管。- 当
closeEvent
返回后,事件循环继续,SubWidget
可能立即被deleteLater
析构。 - 如果
SubWidget
先析构,而线程 3 秒后才发射finished
,信号会投递到一块已释放的内存。 SubWidget
析构后,所有以this
为接收者的connect
自动断开;finished
信号再也找不到对象,于是消失在空气中。
2.方案总览
方案 | 思路 | 是否阻塞 UI | 是否安全 | 适用场景 |
---|---|---|---|---|
方案一:阻塞等待 | 在 closeEvent 里 waitForFinished() | 是 | 安全(但卡 UI) | 非常简单的清理工作,用户可接受假死 |
方案二:异步自毁 | 让 QFutureWatcher 自己 deleteLater() ,不再依赖 this | 否 | 安全 | 真正异步,用户体验好 |
3.方案一:阻塞等待(waitForFinished)
3.1 代码
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 要点
waitForFinished()
会阻塞当前线程(通常是 GUI 线程),界面会卡住 3 秒。- 因为阻塞期间
SubWidget
对象仍在,所以槽函数可以正常调用,不会出现野指针。 - 不需要额外的
deleteLater()
,函数结束后m_pWatcher
作为普通局部变量被销毁即可。
3.3 优缺点
- ✅ 实现简单、无生命周期坑
- ❌ UI 假死;如果任务很长,体验极差
4.方案二:异步自毁(Lambda + deleteLater)
4.1 核心思想
- 把
QFutureWatcher
的生命周期与SubWidget
解耦。 - 用捕获列表
[=]
把pWatcher
拷进 lambda,不再使用this
。 - 任务结束时自己
deleteLater()
,彻底避免野指针。
4.2 代码(简化后)
QFutureWatcher<bool>* SubWidget::createWatcher()
{auto* watcher = new QFutureWatcher<bool>; // 无父对象watcher->setObjectName("watcher_async");// 关键:不连接到 this,而是连接到 lambdaconnect(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; // 调用者可立即返回,无需等待
}
调用方:
void SubWidget::closeEvent(QCloseEvent* e)
{QMainWindow::closeEvent(e);createWatcher(); // 立即返回,不阻塞
}
4.3 要点
- 不依赖
this
:即使SubWidget
马上析构,lambda 仍持有watcher
的拷贝,不会变成悬垂连接。 - 自动销毁:
deleteLater()
把销毁动作排入事件循环,线程结束后回到 GUI 线程时安全析构。 - 无内存泄漏:只要
finished
信号被发射一次,就必然触发deleteLater()
。 - UI 不卡顿:完全符合“异步任务”初衷。
- 两种方案对比总结
维度 | 方案一:阻塞等待 | 方案二:异步自毁 |
---|---|---|
是否卡 UI | 是 | 否 |
代码复杂度 | 低 | 中 |
生命周期安全 | 高 | 高(需遵循不访问 this ) |
适用场景 | 快速退出、简单清理 | 耗时任务、优雅关闭 |
Qt 官方推荐 | ❌ | ✅ |
-
常见踩坑清单
-
在析构函数里
new
一个对象却不delete
→ 内存泄漏或野指针。
-
把
finished
连接到this
槽,但对象先走→ 信号投递到僵尸对象。
-
在
destroyed
信号里再deleteLater(sender())
→ 重复释放,直接崩溃。
-
以为
createWatcher()
返回的裸指针会自动管理→ Qt 只有
QObject
树才会自动析构,普通裸指针不会。
- 一句话结论
- 短任务、可接受假死 → 用方案一
waitForFinished()
,简单粗暴。 - 长任务、要求流畅 → 用方案二 Lambda + deleteLater,彻底摆脱对象生命周期噩梦。