QT + 如何从在不同线程中运行的自定义C++代码调用槽函数

16

我是QT的新手,正在学习一些内容。

我想触发一个从C ++线程(当前为Qthread)修改GUI小部件的槽。

不幸的是,我得到了一个ASSERTION失败:在Q_ASSERT(qApp && qApp->thread() == QThread :: currentThread())处。

这里是一些代码:

(MAIN + Thread类)

   class mythread : public QThread
    {
    public:
        mythread(mywindow* win){this->w = win;};
        mywindow* w;
        void run()
        {
            w->ui.textEdit->append("Hello");        //<--ASSERT FAIL
            //I have also try to call a slots within mywindow which also fail.
        };
    };

    int main(int argc, char *argv[])
    {
        QApplication* a = new QApplication(argc, argv);
        mywindow* w = new mywindow();

        w->show();
        mythread* thr = new mythread(w);
        thr->start();

        return a->exec();
    }

窗口:

class mywindow : public QMainWindow
{
    Q_OBJECT

public:
    mywindow (QWidget *parent = 0, Qt::WFlags flags = 0);
    ~mywindow ();
    Ui::mywindow ui;

private:



public slots:
    void newLog(QString &log);
};

我很好奇如何在不同的线程中通过代码更新GUI部分。

感谢帮助。

4个回答

23

stribika的回答几乎是正确的:

QMetaObject::invokeMethod( textEdit, "append", Qt::QueuedConnection,
                           Q_ARG( QString, myString ) );

cjhuitt是正确的,你通常需要在线程上声明一个信号并将其连接到append()槽上,这样可以免费获得对象生命周期管理(好吧,只需要做一个小的接口更改)。顺便说一下,还有一个额外的参数:

               Qt::QueuedConnection ); // <-- This option is important!

从cjhuitt的答案中获取的内容现在不再必要(在Qt <= 4.1中是必要的),因为connect()默认使用Qt::AutoConnection,这个参数现在(Qt >= 4.2)会根据QThread::currentThread()和接收者QObject的线程亲和性,在emit时切换队列连接和直接连接模式(而不是在连接时考虑发送方和接收方的亲和性)。


2
我不知道连接现在是在发射时建立的...这对于线程对象仍然有效吗?线程对象“存在”于创建它的线程中,而不是在调用QThread::run时生成的线程中。(我们在工作中刚刚就这个问题进行了辩论,并决定在这些情况下指定QueuedConnection选项更为安全。) - Caleb Huitt - cjhuitt
1
我用这个方法来从不是QThread的线程中发出信号。非常适合向Qt主循环发送事件。而且,在Qt 4.3及更高版本中,我从未需要指定QueuedConnection。 - Macke

10

除了stribika的答案,我经常发现使用信号槽连接更容易。您可以在连接时指定它应该是一个排队连接,以避免线程信号在其拥有对象的上下文中出现问题。

class mythread : public QThread
{
signals:
    void appendText( QString );
public:

    mythread(mywindow* win){this->w = win;};
    mywindow* w;
    void run()
    {
        emit ( appendText( "Hello" ) );
    };
};

int main(int argc, char *argv[])
{
    QApplication* a = new QApplication(argc, argv);
    mywindow* w = new mywindow();

    w->show();
    mythread* thr = new mythread(w);
    (void)connect( thr, SIGNAL( appendText( QString ) ),
                   w->ui.textEdit, SLOT( append( QString ) ),
                   Qt::QueuedConnection ); // <-- This option is important!
    thr->start();

    return a->exec();
}

1
如果是从非Qt非UI线程实现,该如何实现呢?(即没有任何Qt依赖的代码;不是QThread子类) - DavidJ
1
@DavidJ 你可以看看其他答案,但我认为在这种情况下,你已经远远超出了Qt信号槽的使用范围,如果你继续把它当作一个槽,可能会遇到困难。然而,所有的槽也都是普通函数,可以像正常调用函数一样被调用(在访问该函数时挂起),因此其他跨线程技术来指示应该调用它也可以起作用。 - Caleb Huitt - cjhuitt

8

@MarcMutz-mmutz,您能否解释一下这段代码有什么问题?我怀疑是SLOT宏的问题,但也可能是您有其他的看法;-) - FourtyTwo
3
invokeMethod 函数只需要函数名("setText"),而不需要 SLOT 的返回结果。 - Marc Mutz - mmutz

2

我认为您不能直接从任何线程调用会导致绘图事件的内容,除了主线程。这将导致程序崩溃。

我认为您可以使用事件循环异步调用内容,以便主GUI线程选择并从主线程进行更新,这就是cjhuitt建议的方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接