【问题标题】:How to emit cross-thread signal in Qt?如何在 Qt 中发出跨线程信号?
【发布时间】:2010-10-12 21:38:08
【问题描述】:

Qt 文档指出信号和槽可以是directqueuedauto

它还指出,如果拥有槽的对象“生活”在与拥有信号的对象不同的线程中,发出这样的信号就像发布消息一样——信号发射将立即返回,槽方法将在目标线程的事件循环中调用.

不幸的是,文档没有指定“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;
    }
};

main.cpp:

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() 永远不会被调用 :(。我做错了什么?

【问题讨论】:

    标签: c++ qt signals-slots qt-signals


    【解决方案1】:

    你的代码有很多问题:

    • 正如 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();
    }
    

    现在 MyObject 将存在于 thread2 中(感谢 moveToThread)。

    MySignal 应该从 thread1 发送(我不确定那个,它可能是从主线程发送的,这并不重要)。

    thread1 中不需要事件循环,因为发出信号不需要事件循环。在线程 2 中需要一个事件循环(由 exec() 启动)来接收信号。

    MySlot 将在 thread2 中调用。

    【讨论】:

    • 有什么方法可以在线程启动之前将插槽连接到信号?我真的不希望线程在所有连接之前发出一些东西。
    • 在我的示例中,连接是在执行发射的线程启动之前完成的。我等到 1000 只是为了好玩 ;)
    • 谢谢,moveToThread 在任何线程启动之前工作。当然 myThread.moveToThread( & myThread );看起来有点奇怪,但效果很好。
    • 严格来说,无论他们是否使用了 emit “关键字”,都不应该有任何影响。它是#defined 到一个空宏。
    • 如果信号是从非 qt 线程(即使用 boost 创建的线程)发出的,这是一个问题,还是有可能?例如,如果我有一个非 qt 网络服务器类,它可以在接收消息时从其内部线程调用回调,并且我想在 QT 项目中调用它并将这些回调路由到 QT 插槽。
    【解决方案2】:

    不要为 Qt 4.4+ 子类化 QThread

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

    本文总结一下:http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/

    缺乏 Qt 部分的文档

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

    如果您使用的是 Qt 4.6 或 4.7,那么您几乎肯定应该继承 QThread。

    使用 moveToThread

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

    【讨论】:

    • Qt 支持子类化QThread 并覆盖run()。它甚至是 Qt 在this table 上提出的第一个解决方案。但是,这仅应在线程不需要事件循环时使用。特别是,如果有人需要在链接文章中显示的线程内调用moveToThread(this);,他使用它是错误的。
    • 这是官方推荐的,直到这篇文章出来:woboq.com/blog/qthread-you-were-not-doing-so-wrong.html我已经实现了线程这种方式,而且非常轻量级。像坦克一样稳定。没有 moveToThread。没有分配额外的 QObjects。您确实可以将工作代码放入 run() 函数中(或从那里调用它)。现在,他们正式支持它。
    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-09-18
      • 1970-01-01
      • 2016-06-13
      • 1970-01-01
      • 2015-08-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多