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 ¶meter) {
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!
}
但是请注意,使用此解决方案,您必须确保计时器的滴答声比处理数据所需的时间慢。否则你会填满一个处理请求队列,这些请求会等待很长时间才能被处理,直到所有请求都得到满足才会让线程完成。
我正在阅读 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 ¶meter) {
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!
}
但是请注意,使用此解决方案,您必须确保计时器的滴答声比处理数据所需的时间慢。否则你会填满一个处理请求队列,这些请求会等待很长时间才能被处理,直到所有请求都得到满足才会让线程完成。