使用QFuture结合QML时程序崩溃问题求助
问题:Q_GADGET实现的Task类在QML调用时崩溃
问题背景
我尝试创建一个可暴露给QML的简易Task类,用C++实现异步操作,为避免继承QObject,采用Q_GADGET宏实现,核心代码如下:
Task类实现
#pragma once #include <QFuture> // Workaround to use a templated class with Q_GADGET class TaskBase { Q_GADGET }; template <typename T> class Task : public TaskBase { public: explicit Task() {} // Creates a task from a QFuture explicit Task(QFuture<T> future) : m_future(future) { auto watcher = new QFutureWatcher<T>(); QObject::connect(watcher, &QFutureWatcher<T>::finished, [this, watcher](){ qDebug () << "Finished, result is" << result(); watcher->deleteLater(); }); watcher->setFuture(future); } // Returns the result of the task Q_INVOKABLE T result() { return m_future.result(); } private: QFuture<T> m_future; }; // Register classes to QML Q_DECLARE_METATYPE(Task<qint64>) Q_DECLARE_METATYPE(Task<QString>) Q_DECLARE_METATYPE(Task<bool>)
调用代码(main.cpp)
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QtConcurrent> #include "task.h" class HeavyComputer : public QObject { Q_OBJECT public: HeavyComputer(QObject *parent = nullptr) : QObject(parent) {} Q_INVOKABLE Task<qint64> compute() const { return Task<qint64>(QtConcurrent::run([](){ qint64 sum = 0; for(int i = 0; i < 100000; ++i){ sum += i; } return sum; })); } }; int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); qRegisterMetaType<Task<qint64>>(); // Create a QML engine. QQmlApplicationEngine engine; // Register the HeavyComputer class qmlRegisterType<HeavyComputer>("HeavyComputer", 1, 0, "HeavyComputer"); // Load the QML file. const QUrl url(QStringLiteral("qrc:/qml/main.qml")); QObject::connect( &engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); // Return the application's exit code. return app.exec(); } #include "main.moc"
QML调用代码
import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Window 2.15 import HeavyComputer 1.0 Window { id: window visible: true width: 640 height: 480 title: qsTr("Hello World") HeavyComputer { id: computer } Button { anchors.bottom: parent.bottom text: "Compute" onClicked: { computer.compute() } } }
崩溃现象
运行程序后多次点击按钮会触发崩溃,调试显示为写入访问违规,崩溃点指向Task类构造函数中的这一行:
qDebug () << "Finished, result is" << result();
栈信息如下:
1 std::_Atomic_storage<QMutexData *,8>::compare_exchange_strong atomic 737 0x7ffb1d1ddec2 2 std::atomic<QMutexData *>::compare_exchange_strong atomic 1600 0x7ffb1d1ddfcc 3 QAtomicOps<QMutexData *>::testAndSetAcquire<QMutexData *> qatomic_cxx11.h 308 0x7ffb1d1d985a 4 QBasicAtomicPointer<QMutexData>::testAndSetAcquire qbasicatomic.h 273 0x7ffb1d1e8354 5 QBasicMutex::fastTryLock qmutex.h 109 0x7ffb1d1df761 6 QMutex::lock qmutex.cpp 230 0x7ffb1d21fb28 7 QMutexLocker::QMutexLocker qmutex.h 234 0x7ffb1d1da5bd 8 QFutureInterfaceBase::waitForResult qfutureinterface.cpp 317 0x7ffb1d236099 9 QFuture<__int64>::result qfuture.h 209 0x7ff7aba58421 10 Task<__int64>::result task.h 27 0x7ff7aba58467 11 <lambda_37427196d3c8e94283745470a735a1b5>::operator() task.h 20 0x7ff7aba56ce1 12 QtPrivate::FunctorCall<QtPrivate::IndexesList<>,QtPrivate::List<>,void,<lambda_37427196d3c8e94283745470a735a1b5>>::call qobjectdefs_impl.h 146 0x7ff7aba575d9 13 QtPrivate::Functor<lambda_37427196d3c8e94283745470a735a1b5>,0>::call<QtPrivate::List<>,void> qobjectdefs_impl.h 257 0x7ff7aba53e43 14 QtPrivate::QFunctorSlotObject<lambda_37427196d3c8e94283745470a735a1b5>,0,QtPrivate::List<>,void>::impl qobjectdefs_impl.h 449 0x7ff7aba57d4e 15 QtPrivate::QSlotObjectBase::call qobjectdefs_impl.h 398 0x7ffb1d64e9d2 16 doActivate<0> qobject.cpp 3886 0x7ffb1d6a6bb8 17 QMetaObject::activate qobject.cpp 3947 0x7ffb1d6936d7 18 QFutureWatcherBase::finished moc_qfuturewatcher.cpp 274 0x7ffb1d23a5f3 19 QFutureWatcherBasePrivate::sendCallOutEvent qfuturewatcher.cpp 442 0x7ffb1d23af0e 20 QFutureWatcherBase::event qfuturewatcher.cpp 336 0x7ffb1d23a581 ... <More>
补充:仅从QML调用HeavyComputer::compute()时崩溃,C++直接调用以下代码运行正常:
HeavyComputer computer; computer.compute();
原因分析
核心问题是Task对象的生命周期管理错误:
- QML调用
compute()时,返回的Task是值类型,QML会在调用结束后立即销毁这个临时对象(未持有任何引用)。 - 但Task构造函数中创建的QFutureWatcher会在异步操作完成后触发
finished信号,此时lambda捕获的this已经指向被销毁的Task对象,访问m_future会触发野指针访问,导致崩溃。 - C++直接调用时,Task对象会在当前作用域结束前保持存活,异步操作完成时对象仍有效,因此不会崩溃。
解决方案
方案1:将Task改为QObject子类(推荐)
放弃Q_GADGET,改用QObject继承,让QML可以持有Task对象的引用,延长其生命周期:
修改后的Task类
#pragma once #include <QFuture> #include <QFutureWatcher> #include <QObject> template <typename T> class Task : public QObject { Q_OBJECT public: explicit Task(QObject *parent = nullptr) : QObject(parent) {} explicit Task(QFuture<T> future, QObject *parent = nullptr) : QObject(parent), m_future(future) { auto watcher = new QFutureWatcher<T>(this); // 以Task为父对象,自动管理生命周期 connect(watcher, &QFutureWatcher<T>::finished, this, [this](){ qDebug () << "Finished, result is" << result(); emit taskFinished(result()); // 发送信号通知QML结果 }); watcher->setFuture(future); } Q_INVOKABLE T result() { return m_future.result(); } signals: void taskFinished(T result); // 新增信号,让QML监听结果 private: QFuture<T> m_future; }; // 注册指针类型的元类型 Q_DECLARE_METATYPE(Task<qint64>*)
修改HeavyComputer的compute方法
Q_INVOKABLE Task<qint64>* compute() const { auto future = QtConcurrent::run([](){ qint64 sum = 0; for(int i = 0; i < 100000; ++i){ sum += i; } return sum; }); return new Task<qint64>(future); }
修改main.cpp的元类型注册
qRegisterMetaType<Task<qint64>*>(); qmlRegisterType<Task<qint64>>("Task", 1, 0, "Task");
修改QML调用逻辑
Button { anchors.bottom: parent.bottom text: "Compute" onClicked: { let task = computer.compute() task.taskFinished.connect((result) => { console.log("Task finished, result:", result) task.deleteLater() // 手动释放内存 }) } }
方案2:不依赖Task对象,直接捕获QFuture拷贝
如果坚持使用Q_GADGET,可以修改lambda逻辑,避免捕获this,改为捕获QFuture的拷贝:
template <typename T> class Task : public TaskBase { public: explicit Task() {} explicit Task(QFuture<T> future) : m_future(future) { auto watcher = new QFutureWatcher<T>(); // 捕获future的拷贝,而非this QObject::connect(watcher, &QFutureWatcher<T>::finished, [watcher, future](){ qDebug () << "Finished, result is" << future.result(); watcher->deleteLater(); }); watcher->setFuture(future); } Q_INVOKABLE T result() { return m_future.result(); } private: QFuture<T> m_future; };
该方案仅适合不需要在QML中主动调用result()的场景,因为Task对象仍会被QML快速销毁,无法安全访问其成员。
内容的提问来源于stack exchange,提问作者devinbed
相关产品推荐
相关产品推荐

