Qt6多线程问题

Qt6 multi-threading issue

我正在阅读 this and this 文档。 但是我还是多线程的新手,我不能完全理解这些主题。

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

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);

但它 returns 如此长的编译错误列表,我想这是一个非常错误的方法。 所以我想尝试 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);
};

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

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

时仍然阻塞

我错过了什么?

您不能直接从主线程调用 doWork(),因为那样的话它会在主线程中被调用,然后被阻塞。正如您现在观察到的那样。

因此,您触发 worker 在辅助线程中完成工作的正确方法是此连接:

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

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

但是您的代码中有更多错误。你不应该连接到 deleteLater() 因为你的线程是你的主 class 的成员,并且当你的主 class 被销毁时将被删除。如果您在此之前调用 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();

但是因为您没有退出事件循环,所以您需要在删除线程之前手动退出它。最好的地方是在你的 main class.

的析构函数中
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!
}

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