在 moveToThread() 之后未调用 Qt 4.8 信号/插槽

Posted

技术标签:

【中文标题】在 moveToThread() 之后未调用 Qt 4.8 信号/插槽【英文标题】:Qt 4.8 Signals/Slots not called after moveToThread() 【发布时间】:2014-08-12 14:22:39 【问题描述】:

我有一个派生自 QObjectUploadWorker 的类,它已使用 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 信号/插槽的主要内容,如果未能解决你的问题,请参考以下文章

Qt 多线程使用moveToThread

QT多线程之---moveToThread用法

Qt moveToThread:对象带来了哪些资源?

Qt源码阅读 moveToThread

Qt moveToThread 仅在第一次工作

Qt 线程关联和 moveToThread 的问题