如何在Qt中发出跨线程信号?

62

Qt的文档表明信号和槽可以是直接(direct)队列(queued)自动(auto)的。

它还指出,如果拥有槽的对象所在的线程与发射信号的对象不同,那么发射该信号将会像发送消息一样——信号的发射会立即返回,并且槽方法将在目标线程的事件循环中被调用。

遗憾的是,文档没有准确解释"所在(lives)"是什么意思,并且没有提供示例。我已经尝试了以下代码:

main.h:

class CThread1 : public QThread
{
Q_OBJECT
public:
    void run( void )
    {
        msleep( 200 );
        std::cout << "thread 1 started" << std::endl;
        MySignal();
        exec();
    }
signals:
    void MySignal( void );
};

class CThread2 : public QThread
{
Q_OBJECT
public:
    void run( void )
    {
        std::cout << "thread 2 started" << std::endl;
        exec();
    }
public slots:
    void MySlot( void )
    {
        std::cout << "slot called" << std::endl;
    }
};
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    CThread1 oThread1;
    CThread2 oThread2;
    QObject::connect( & oThread1, SIGNAL( MySignal() ),
        & oThread2, SLOT( MySlot() ) );
    oThread1.start();
    oThread2.start();
    oThread1.wait();
    oThread2.wait();
    return a.exec();
}

输出结果为:

thread 2 started
thread 1 started

MySlot()从未被调用 :(。我做错了什么?

3个回答

50

你的代码存在一些问题:

  • 像Evan所说,emit关键字缺失。
  • 你的所有对象都在主线程中,只有run方法中的代码才在其他线程中运行,这意味着MySlot槽将在主线程中被调用,我不确定这是否符合你的要求。
  • 由于主事件循环从未启动,因此永远不会调用你的槽:你对wait()的两次调用只会在非常长的时间后超时(你可能会在此之前杀死应用程序),而且我认为这也不是你想要的,无论如何,它们在你的代码中真的没有用处。

这段代码很可能可以工作(虽然我没有测试过),我认为它能够实现你想要的功能:

class MyObject : public QObject
{
    Q_OBJECT
public slots:
    void MySlot( void )
    {
        std::cout << "slot called" << std::endl;
    }
};

class CThread1 : public QThread
{
    Q_OBJECT
public:
    void run( void )
    {
        std::cout << "thread 1 started" << std::endl;
        int i = 0;
        while(1)
        {
           msleep( 200 );
           i++;
           if(i==1000)
              emit MySignal();
        }
    }
signals:
    void MySignal( void );
};

class CThread2 : public QThread
{
    Q_OBJECT
public:
    void run( void )
    {
        std::cout << "thread 2 started" << std::endl;
        exec();
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    CThread1 oThread1;
    CThread2 oThread2;
    MyObject myObject;
    QObject::connect( & oThread1, SIGNAL( MySignal() ),
        & myObject, SLOT( MySlot() ) );
    oThread2.start();
    myObject.moveToThread(&oThread2)
    oThread1.start();
    return a.exec();
}

现在,由于使用了moveToThread函数,MyObject会在thread2中运行。

MySignal应该从thread1发送(虽然我不确定,它可能会从主线程发送,但这并不重要)。

在thread1中不需要事件循环,因为发射信号不需要事件循环。在thread2中需要一个事件循环(通过exec()启动)以接收信号。

MySlot将在thread2中被调用。


有没有办法在线程启动之前将槽连接到信号?我真的不希望在所有东西连接之前线程发出信号。 - grigoryvp
在我的示例中,在启动发射线程之前已经完成了连接。我等待1000只是为了好玩 ;) - Aiua
谢谢,moveToThread在任何线程开始之前都可以工作。当然,myThread.moveToThread(&myThread)看起来有点奇怪,但是完全正常运行。 - grigoryvp
23
严格来说,无论他们是否使用“关键字”emit都不应该影响任何事情。它被#define为一个空的宏。 - Michael Bishop
如果信号是从非Qt线程(例如使用boost创建的线程)发出的,那会有问题吗?或者这是否可能?例如,如果我有一个非Qt网络服务器类,可以在其内部线程接收消息时调用回调,并且我想在QT项目中使用它并将这些回调路由到QT插槽中。 - shadow_map

40

不要再Qt 4.4以上的版本中继承QThread类

Aiua的回答虽然很好,但我想指出一些关于Qt 4.6或4.7和QThread的问题。

这篇文章总结了这个问题: http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/

Qt文档缺乏更新

遗憾的是,这个问题源自于Qt文档缺乏更新。在Qt 4.4之前,QThread没有默认的run()实现,这意味着你必须继承QThread才能使用它。

如果你正在使用Qt 4.6或4.7,那么你几乎肯定不应该继承QThread类。

使用moveToThread方法

让槽函数在工作线程中执行的关键是使用像Aiua所指出的moveToThread方法。


1
Qt支持通过子类化QThread和重写run()方法来创建线程。甚至在这个表格中,Qt建议首先使用这种方式。但是,只有在线程不需要事件循环时才应使用它。特别是,如果有人需要在线程内调用moveToThread(this);,就像链接的文章中所示,那么他使用的方法是错误的。 - JojOatXGME
1
这是官方的建议,直到这篇文章出现:https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html我已经以这种方式实现了线程,它非常轻量级。稳定如坦克。没有 moveToThread。没有额外分配的 QObjects。你确实可以将工作代码放在 run() 函数中(或从那里调用它)。现在,官方支持它。 - CodeLurker

0

你应该发出信号来启动你的线程函数,例如:

emit operateCut(examId,examName_examTemplate[examName].studentIdRec,examName_examTemplate[examName].choiceRecA,examName_examTemplate[examName].choiceRecB,examName_examTemplate[examName].objectRecA,examName_examTemplate[examName].objectRecB);

你可以在此信号中添加多个参数


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