当前位置: 首页 > 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.xdnf.cn/news/1207063.html

相关文章:

  • Mac m系列芯片安装node14版本使用nvm + Rosetta 2
  • 【Rust并发集合】如何在多线程中并发安全地使用集合
  • vue3插槽详解
  • Deep Research(信息检索增强)认识和项目实战
  • 设计模式---单例
  • 博物馆 VR 导览:图形渲染算法+智能讲解技术算法实现及优化
  • 【MySQL】从连接数据库开始:JDBC 编程入门指南
  • 如何从 Web2 转型到 Web3
  • 01 基于sklearn的机械学习-机械学习的分类、sklearn的安装、sklearn数据集、数据集的划分、特征工程中特征提取与无量纲化
  • 使用JavaScript实现轮播图的任意页码切换和丝滑衔接切换效果
  • Linux之网络部分-应用层协议 HTTP
  • GaussDB 数据库架构师修炼(十) 性能诊断常用视图
  • BGP高级特性之ORF
  • 【C++】第十九节—一文万字详解 | AVL树实现
  • C51 中断
  • mangoDB面试题及详细答案 117道(026-050)
  • 电商修图,尺寸修改适配各大电商平台
  • 第17章——多元函数积分学的预备知识
  • VMware Workstation Pro 详细安装步骤
  • Linux->模拟实现 fopen/fwrite/fclose
  • jQuery 最新语法大全详解(2025版)
  • Uniswap V2 成功上线 PolkaVM:Polkadot Hub 的里程碑时刻
  • 结合项目阐述 设计模式:单例、工厂、观察者、代理
  • OSPF笔记
  • 图片上传 el+node后端+数据库
  • 4.应用层自定义协议与序列化
  • 生物信息学数据技能-学习系列001
  • 深入Go并发编程:Channel、Goroutine与Select的协同艺术
  • Deepseek + browser-use 轻松实现浏览器自动化
  • MCP error -32000: Connection closed