【问题标题】:Qt6 multi-threading issueQt6多线程问题
【发布时间】:2022-01-21 03:47:09
【问题描述】:

我正在阅读 thisthis 文档。 但是我对多线程仍然很陌生,我无法完全理解这些主题。

Ubuntu 20.04 下的 Qt 6.2.0。 基本上我有这个功能:

bool Flow::checkNfc()
{
    QByteArray id;
    QByteArray data;

    bool ret = _nfc.read(&id, &data, 8);
    if (ret)
    {
        // do something
    }

    return ret;
}

它试图读取一个 NFC 标签,如果找到它,就会做一些事情。 这个函数:

bool ret = _nfc.read(&id, &data, 8);

依次调用一些阻塞当前线程的libnfc 函数。 我只需要在另一个线程中执行这个函数,以避免我的应用程序“卡顿”。

因为checkNfc_nfc.read 函数都需要与主线程交换数据,我不确定是否可以使用QFutureWatcher 方法。我试过类似的东西:

QFutureWatcher<bool> watcher;
QFuture<bool> future = QtConcurrent::run(&MyProject::checkNfc);
watcher.setFuture(bool);

但是它返回了一个很长的编译错误列表,我猜这是一个非常错误的方法。 所以我想试试QThread 解决方案。问题是示例对于真实案例场景来说太简单了:

class Worker : public QObject
{
    Q_OBJECT

public slots:
    void doWork(const QString &parameter) {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        emit resultReady(result);
    }

signals:
    void resultReady(const QString &result);
};

无论如何我都试过了,在我的主要课程中我写道:

private:
    QThread _nfcThread;
    MyNfc _nfc;

private slots:
    void nfc_readResult(bool success, QByteArray id, QByteArray data);

在构造函数中:

_nfc.moveToThread(&_nfcThread);
connect(&_nfcThread, &QThread::finished, &_nfc, &QObject::deleteLater);
connect(&_nfc, &MyNfc::resultRead, this, &MyProject::nfc_readResult);
_nfcThread.start();

从一个计时器槽:

_nfc.doWork();

在 MyNfc 中:

signals:
    void resultRead(bool result, QByteArray id, QByteArray data);

public slots:
    void doWork();

和:

void MyNfc::doWork()
{
    QByteArray id;
    QByteArray data;

    bool ret = read(&id, &data, 8);
    emit resultRead(ret, id, data);
}

一切仍在工作...但每次我调用 doWork() 时,我的主应用程序仍然阻塞。

我错过了什么?

【问题讨论】:

    标签: multithreading qt qthread qt6


    【解决方案1】:

    您不能直接从主线程调用doWork(),因为在这种情况下,它会在主线程中被调用,然后会被阻塞。和你现在观察到的完全一样。

    所以触发worker在辅助线程中工作的正确方法是这个连接:

    connect(&_nfcThread, &QThread::started, &_nfc, &MyNfc::doWork);
    

    然后你只需要启动你的线程,当它启动时它会调用doWork()

    但是您的代码中存在更多错误。你不应该连接到deleteLater(),因为你的线程是你的主类的成员,当你的主类被销毁时将被删除。如果你之前打电话给deleteLater(),你会得到双重删除,这是未定义的行为。

    所以你的代码的重要部分应该是:

    _nfc.moveToThread(&_nfcThread);
    connect(&_nfcThread, &QThread::started, &_nfc, &MyNfc::doWork); // this starts the worker
    connect(&_nfc, &MyNfc::resultRead, this, &MyProject::nfc_readResult); // this passes the result
    connect(&_nfc, &MyNfc::resultRead, &_nfcThread, &QThread::quit); // this will quit the event loop in the thread
    _nfcThread.start();
    

    更新:如果您想定期调用插槽,请连接到计时器。工作完成后不要退出线程。最简单的情况是:

    _nfc.moveToThread(&_nfcThread);
    connect(&_nfcThread, &QThread::started, &_timer, &QTimer::start); // this starts the timer after the thread is ready
    connect(&_timer, &QTimer::timeout, &_nfc, &MyNfc::doWork); // start work at timer ticks
    connect(&_nfc, &MyNfc::resultRead, this, &MyProject::nfc_readResult); // this passes the result
    _nfcThread.start();
    

    但是,由于您没有退出事件循环,因此您需要在删除线程之前手动退出它。最好的地方是主类的析构函数。

    MainClass::~MainClass()
    {
      _nfcThread.quit(); // this schedules quitting of the event loop when the thread gets its current work done
      _nfcThread.wait(); // this waits for it, this is important! you cannot delete a thread while it is working on something.
      // ... because the thread (and the worker too) will get deleted immediately once this destructor body finishes, which is... right now!
    }
    

    但是请注意,使用此解决方案时,您必须确保计时器滴答声比处理数据所需的时间慢。否则,您将填满一个处理请求队列,这些请求将等待很长时间才能处理,并且在所有请求都得到满足之前不会让线程完成。

    【讨论】:

    • 啊!知道了。但我需要定期运行doWork。得到结果后我应该终止线程吗?所以在我的计时器槽中我再次启动线程?
    • 是的,现在可以了。最重要的是我明白了原因!
    • 我在答案中添加了 UPDATE 以允许您在一个线程中定期调用它。
    • 是的,完全正确。这是我的错误,我现在已经解决了。我看到您现在了解线程逻辑。 :)
    • @Alexander 你是对的。我要重写文本。
    猜你喜欢
    • 2013-06-02
    • 1970-01-01
    • 1970-01-01
    • 2011-02-20
    • 2015-02-06
    • 2016-06-23
    相关资源
    最近更新 更多