在 moveToThread() 之后未调用 Qt 4.8 信号/插槽
Posted
技术标签:
【中文标题】在 moveToThread() 之后未调用 Qt 4.8 信号/插槽【英文标题】:Qt 4.8 Signals/Slots not called after moveToThread() 【发布时间】:2014-08-12 14:22:39 【问题描述】:我有一个派生自 QObject
、UploadWorker
的类,它已使用 Qt 文档中所展示的在线程中运行任务的推荐方式启动。
QThread* thread = new QThread();
UploadWorker* worker = new UploadWorker();
worker->moveToThread(thread);
connect(thread, SIGNAL(started()), worker, SLOT(doWork()));
现在,效果很好。然后我的UploadWorker
尝试使用相同的技术为自己创建一个名为Uploader
的工人。
这是标题的必要部分。
class UploadWorker : public QObject
Q_OBJECT
public:
// stuff
public slots:
void doWork();
signals:
void allWorkDone();
protected:
void startUploader();
;
class Uploader : public QObject
Q_OBJECT
public:
// stuff
public slots:
void doWork();
void finishWhenQueueIsEmpty();
;
这是UploadWorker
的实现。
void
UploadWorker::doWork()
// This method is called when QThread emits started()
// Prepare the upload thread
startUploader();
// Do some important work...
// Notify the upload thread that we're done
emit allWorkDone();
void
UploadWorker::startUploader()
QThread* thread = new QThread();
Uploader* uploader = new Uploader();
uploader->moveToThread(thread);
connect(this, SIGNAL(stopped()), uploader, SLOT(stop()));
connect(uploader, SIGNAL(finished()), thread, SLOT(quit()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
connect(uploader, SIGNAL(finished()), uploader, SLOT(deleteLater()));
connect(thread, SIGNAL(started()), uploader, SLOT(doWork()));
connect(this, SIGNAL(allWorkDone()),
uploader, SLOT(finishWhenQueueIsEmpty()));
thread->start();
我现在的问题是,Uploader::finishWhenQueueIsEmpty()
插槽永远不会被触发。除非:我删除了uploader->moveToThread(thread);
。
错误在哪里?
编辑
我忘了提到Uploader::doWork()
方法是在线程启动时调用的。它是任何其他插槽,在此示例中 finishWhenQueueIsEmpty()
不起作用。
我修正了关于插槽名称的错字。
【问题讨论】:
所以您确认使用moveToThread()
永远不会触发插槽,但是使用它是吗?
doWork 还忙吗?
@ratchetfreak which doWork
你的意思是,有两个? @JBL 如果我不将 uploader
移动到 thread
则触发插槽,是的。
【参考方案1】:
一个非常可能的可能性是,您正在执行您的 Uploader::doWork()
槽并等待对槽 Uploader::finishWhenQueueIsEmpty()
的调用以通过某种同步机制(例如布尔值 isRunning
)完成其执行。
然后你需要做的是连接你的插槽,如下所示:
connect(this, SIGNAL(allWorkDone()),
uploader, SLOT(finishWhenQueueIsEmpty()),
Qt::DirectConnection);
并在finishWhenQueueIsEmpty()
中使用互斥锁保护同步机制。
为什么?
因为在不同线程中的对象之间建立的连接的默认模式是Qt::QueuedConnection
。它在这里所做的是,这里的Uploader
类的每个槽都排队等待执行,即一旦线程返回事件循环(即一旦当前槽完成运行),队列中的下一个槽将运行。那么,也就意味着在这种模式下,调用Uploader::doWork()
再调用Uploader::finishWhenQueueIsEmpty()
会导致程序卡在doWork()
。
使用Qt::DirectConnection
,插槽将在运行UploaderWorker::doWork()
的线程中运行,并且实际上可以在Uploader::doWork()
运行时运行。
但是,您有两个线程可以访问同一个 Uploader
对象,因此您需要保护对该对象部分的每次访问/写入。
【讨论】:
天哪,我没想过用这个结构来阻塞事件循环。当然,Qt::DirectConnection
现在说得通了。谢谢!
@JBL,正如我们所怀疑的,部分代码未发布。推得好。
这真是太棒了!谢谢!【参考方案2】:
void
UploadWorker::doWork()
// This method is called when QThread emits started()
// Prepare the upload thread
startUploader();
// Do some important work...
// Notify the upload thread that we're done
emit allWorkDone();
这里的结构告诉我,在你真正完成“重要工作”之前不要让事件循环运行,这意味着不会触发任何插槽。
要么通过定期调用QApplication::processEvents();
让事件运行,要么重新设计该工作,以便由事件循环中的一系列调用处理。
【讨论】:
信号 allWorkDone() 不会排队,然后 doWork 会退出,然后允许事件循环运行并处理信号吗? 您的答案几乎与 JBL 的答案一样正确。它确实帮助我理解了我的错误。谢谢! 这个答案是我的解决方案,因为从未调用过的插槽位于 QThread 中。更改连接类型无效。【参考方案3】:在 Uploader 类中,您标记插槽:void finishedWhenQueueIsEmpty();
然后你调用:-
connect(this, SIGNAL(allWorkDone()), uploader, SLOT(finishWhenQueueIsEmpty()));
所以一个是'finishWhen...',另一个是'finishedWhen..'
如果您使用这种连接语法(Qt 5.0 之前),那么您可以检查连接调用的返回值,或者至少查看在调试器中运行时的调试输出,它会告诉您插槽是无效。
使用 Qt 5 连接语法,这可以在编译时捕获:-
connect(this, &UploadWorker::allWorkDone() uploader, &Uploader::finishWhenQueueIsEmpty());
【讨论】:
@JBL connect 调用引用了带有“finish..”的上传器对象,但是上传器类声明了“finished...”发布的 UploadWorker 类中没有这样的信号或槽。我错过了什么? 我看到了 UploadWorker 和 Uploader 类声明。我同意 Merlin069。 @JBL,上传者类声明是在发布的代码中声明的,就在 UploadWorker 类声明之后。 抱歉,确实,两者都有。但是奇怪的是,OP 报告说仅删除 moveToThread 就可以使其工作。 (也许发布的代码与他正在使用的代码不是最新的......) @JBL 我猜 moveToThread 的问题可能不是贴出的代码的一部分,但是连接中信号的错误命名仍然存在。以上是关于在 moveToThread() 之后未调用 Qt 4.8 信号/插槽的主要内容,如果未能解决你的问题,请参考以下文章