我遇到了Qt Threads和Connections的麻烦。我找到了关于这个主题的几个教程和讨论,我跟着this tutorial来创建线程。但是我仍然遇到了问题,在线程上调用wait()永远不会返回并且UI冻结。
之前有人问过类似的问题(第二个例子): Qt connection type between threads: why does this work?
在问题的最后一次编辑中,作者提到他造成了僵局。我假设,我在我的应用程序中也这样做。但我仍然不明白,为什么会这样。阅读suggested article并没有帮助我理解。我只是明白了,死锁可能发生,但我不知道,是什么导致它或在我的情况下。
我还创建了一个简化为核心问题的例子。找到这个问题底部的代码。
所以我的问题是: 在我的例子中,死锁的原因究竟是什么? 是否有解决方案而没有建立直接连接?
我真的很感激任何暗示。
谢谢!
修改
由于这些注释,我试图通过信号发送停止请求,并在线程循环中添加了一个QCoreApplication :: processEvents()调用。但主要问题仍然是一样的。
EDIT2:
在考虑了一些关于事件循环的内容之后,我找到了一个可接受的解决方案:
thread.requestStop();
// now instead of using wait(), we poll and keep the event loop alive
// polling is not nice, but if it does not take a very long time
// for the thread to finish, it is acceptable for me.
while (thread.isRunning())
{
// This ensures that the finished() signal
// will be processed by the thread object
QCoreApplication::processEvents();
}
这实际上有效,工人本身也控制着如何停止工作。
在提出这个问题后,我也解释了冻结问题:调用等待似乎使主线程保持忙或暂停,因此它不会处理任何事件。由于线程对象存在于主线程中,因此线程的finished()信号被命令但从未处理过。
我的隐含假设,即thread.wait()仍会使事件循环保持工作,显然是错误的。但是,QThread :: wait()函数对于什么有用?!?
这只是一个理论,但也许有人可以验证或伪造它......
编辑3(最终解决方案):
在阅读this small article并阐述子类解决方案后,我认为这对于这个特定问题更为可取。不需要事件循环,我可以直接调用不同的线程并使用互斥保护。代码更少,更容易理解,也更容易调试。
我想我只会使用非子类化策略,如果与线程的交互多于开始和暂停的话。
我的缩减示例
也许我应该指出,我不删除该帖子,因为在我的原始应用程序中,我想稍后恢复,所以停止它实际上意味着暂停它。
worker.h:
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
#include <QMutex>
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject* parent = NULL);
public slots:
void doWork();
void requestStop();
signals:
void finished();
private:
bool stopRequested;
QMutex mutex;
};
#endif // WORKER_H
worker.cpp:
#include "worker.h"
#include <QThread>
#include <iostream>
using namespace std;
Worker::Worker(QObject *parent)
: stopRequested(false)
{
}
void Worker::doWork()
{
static int cnt = 0;
// local loop control variable
// to make the usage of the mutex easier.
bool stopRequesteLocal = false;
while (!stopRequesteLocal)
{
cout << ++cnt << endl;
QThread::msleep(100);
mutex.lock();
stopRequesteLocal = stopRequested;
mutex.unlock();
}
cout << "Finishing soon..." << endl;
QThread::sleep(2);
emit finished();
}
void Worker::requestStop()
{
mutex.lock();
stopRequested = true;
mutex.unlock();
}
主程序:
#include <QCoreApplication>
#include <QThread>
#include <QtCore>
#include <iostream>
#include "worker.h"
using namespace std;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThread thread;
Worker worker;
QObject::connect(&thread, SIGNAL(started()), &worker, SLOT(doWork()));
// this does not work:
QObject::connect(&worker, SIGNAL(finished()), &thread, SLOT(quit()));
// this would work:
//QObject::connect(&worker, SIGNAL(finished()), &thread, SLOT(quit()), Qt::DirectConnection);
// relocating the moveToThread call does not change anything.
worker.moveToThread(&thread);
thread.start();
QThread::sleep(2);
worker.requestStop();
cout << "Stop requested, wait for thread." << endl;
thread.wait();
cout << "Thread finished" << endl;
// I do not know if this is correct, but it does not really matter, because
// the program never gets here.
QCoreApplication::exit(0);
}
答案 0 :(得分:2)
我看到的第一个问题是你没有使用信号和插槽在不同线程上运行的对象之间进行通信; main和承载worker对象的新线程。
您将worker对象移动到第二个线程,但是从主线程调用worker对象上的函数: -
thread.start();
QThread::sleep(2);
worker.requestStop(); // Aaahh, this is running on the new thread!!!
考虑到一个线程有自己的堆栈和寄存器,我真的不知道这是多么安全。
如果使用信号和插槽,Qt会处理很多线程问题。虽然您应该能够使用变量来控制第二个线程,但使用信号和插槽也会更清晰。
请注意,当从一个线程发出信号时,如果发送方和接收方位于不同的线程上,则会将消息发布到接收对象的线程。
转换代码以使用信号和插槽在不同线程上的对象之间进行通信,并且您的死锁应该消失。
答案 1 :(得分:2)
我在问题文本中添加了自己的回答,如EDIT 3。
答案 2 :(得分:0)
看起来你没看完那篇文章。
QThread* thread = new QThread;
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString)));
connect(thread, SIGNAL(started()), worker, SLOT(process()));
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
您仅部分实施了文章中建议的内容。
QThread::wait()
将等到QThread::finished()
发出doWork()
。如果您需要从此线程退出并返回主线程,请发出此SIGNAL
。为此,您需要保留此对象移动到的线程的引用。