You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 02:20:39