当前位置: 首页 > news >正文

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 ...

根本原因:

  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 代码

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 代码(简化后)

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 要点

  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,彻底摆脱对象生命周期噩梦。
http://www.lryc.cn/news/604167.html

相关文章:

  • 02-Breakout靶机攻略
  • linux命令ps的实际应用
  • ubuntu18.04制作raid0
  • Springboot+vue智能家居商城的设计与实现
  • python使用ffmpeg录制rtmp/m3u8推流视频并按ctrl+c实现优雅退出
  • Apache Ignite 的分布式队列(IgniteQueue)和分布式集合(IgniteSet)的介绍
  • windows下Docker安装路径、存储路径修改
  • Element Plus常见基础组件(一)
  • 网络协议——MPLS(多协议标签转发)
  • Day23-二叉树的层序遍历(广度优先搜素)
  • 基于dcmtk的dicom工具 第九章 以json文件或sqlite为数据源的worklist服务(附工程源码)
  • Mqttnet的MqttClientTlsOptions.CertificateValidationHandler详解
  • SQL 怎么学?
  • SQLAlchemy 全方位指南:从入门到精通
  • Linux初学者在CentOS 7虚拟机中rpm、yum、dnf的操作练习
  • PCIE4.0/5.0/DDR4/DDR5使用以及布局布线规则-集萃
  • 14、distance_object_model_3d算子
  • 粒子群优化算法(Particle Swarm Optimization, PSO) 求解二维 Rastrigin 函数最小值问题
  • 三相四桥臂SVPWM控制及电机模型
  • Excel制作滑珠图、哑铃图
  • CSRF漏洞原理及利用
  • 子数组和 问题汇总
  • Mysql缓冲池和LRU
  • Accessibility Insights for Windows 使用教程
  • Adv. Sci. 前沿:非零高斯曲率3D结构可逆转换!液晶弹性体多级形变新策略
  • Javaweb————HTTP请求头属性讲解
  • [leetcode] 电话号码的排列组合
  • Vue El 基础
  • PyTorch 数据类型和使用
  • 第二课 P-MOS管应用