zoukankan      html  css  js  c++  java
  • Qt QThread与QObject的关系

    Threads and QObjects

    QThread 继承 QObject.。它可以发送started和finished信号,也提供了一些slot函数。

    QObject.可以用于多线程,可以发送信号调用存在于其他线程的slot函数,也可以postevent给其他线程中的对象。之所以可以这样做,是因为每个线程都有自己的事件循环。

    在进行下面的讲解之前,应该了解的重要的一点是:QThread 对象所在的线程,和QThread 创建的线程,也就是run()函数执行的线程不是同一个线程。QThread 对象所在的线程,就是创建对象的线程。我们通过一个例子说明更能清楚一点:

     1 MyThread::MyThread(QObject *parent /* = NULL */):QThread(parent)
     2 
     3 {
     4        qDebug()<<"MyThreadobject currentThreadId :"<<QThread::currentThreadId();
     5 
     6 }
     7 
     8  
     9 
    10 void MyThread::run()
    11 
    12 {
    13        qDebug()<<"run()  currentThreadId : "<<QThread::currentThreadId();
    14 
    15 }
    16 
    17  
    18 
    19 int main(int argc, char *argv[])
    20 
    21 {
    22        QApplication a(argc, argv);
    23 
    24        MyThread thread;
    25 
    26        qDebug()<<"mainThread : "<<QThread::currentThreadId();
    27 
    28        thread.start();
    29 
    30        returna.exec();
    31 
    32 }

    输出结果:MyThread所在的线程就是主线程,run()函数是新开的线程。

    QObject Reentrancy
    QObject.是可重入的,它的大多数非GUI子类,例如QTimer, QTcpSocket, QUdpSocket and QProcess都是可重入的,使得这些类可以同时用于多线程。需要注意的是,这些类设计为从一个单一的线程创建和使用的,在一个线程创建对象,而从另外一个线程调用对象的函数并不能保证行得通。有三个限制需要注意:

    1.    QObject的子对象必须在创建其parent的线程中创建。这意味着,你不能把QThread对象作为parent传递给创建在线程中的对象,因为QThread 对象本身在另外一个线程中创建。

    2.    事件驱动对象只能用于单线程。尤其是在定时器机制和网络模块。例如,你不能在不是对象所处的线程start一个计时器或者链接一个secket。简单的说就是,你不能在线程A创建了一个计时器timer,然后在线程B从启动timer。

    我们可以验证一下:

     1 class MyThread : publicQThread
     2 
     3 {
     4        Q_OBJECT
     5 
     6 public:
     7 
     8        MyThread(QObject *parent = NULL);
     9 
    10        ~MyThread();
    11 
    12  
    13 
    14 public slots:
    15 
    16        voidtimeOutSlot();
    17 
    18 protected:
    19 
    20        voidrun();
    21 
    22        QTimer *m_pTimer;
    23 
    24  
    25 
    26 };
    27 
    28  
    29 
    30 MyThread::MyThread(QObject*parent /* = NULL */):QThread(parent)
    31 
    32 {
    33        m_pTimer = newQTimer(this);
    34 
    35        qDebug()<<"MyThreadobject currentThreadId :"<<QThread::currentThreadId();
    36 
    37        connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));
    38 
    39 }
    40 
    41  
    42 
    43 void MyThread::timeOutSlot()
    44 
    45 {
    46        qDebug()<<"timer  timeout ";
    47 
    48 }
    49 
    50  
    51 
    52 MyThread::~MyThread()
    53 
    54 {
    55  
    56 
    57 }
    58 
    59  
    60 
    61 void MyThread::run()
    62 
    63 {
    64        m_pTimer->start(500);
    65 
    66        qDebug()<<"run()  currentThreadId : "<<QThread::currentThreadId();
    67 
    68        qDebug( "finish!");
    69 
    70 }
    71 
    72  
    73 
    74  
    75 
    76  
    77 
    78  intmain(int argc, char*argv[])
    79 
    80 {
    81        QApplication a(argc, argv);
    82 
    83        MyThread thread;
    84 
    85        qDebug()<<"mainThread : "<<QThread::currentThreadId();
    86 
    87        thread.start();
    88 
    89        returna.exec();
    90 
    91 }

    Timeout函数并没有被调用。我们还发现有多了一行输出:QObject::startTimer: timers cannot be startedfrom another thread

    跟踪timer的start源码,我们发现:

     1 void QEventDispatcherWin32::registerTimer(int timerId, intinterval, QObject *object)
     2 
     3 {
     4     if (timerId< 1 || interval < 0 || !object) {
     5         qWarning("QEventDispatcherWin32::registerTimer:invalid arguments");
     6 
     7         return;
     8 
     9 }
    10 
    11  else if(object->thread() != thread() || thread() != QThread::currentThread())
    12 
    13 {
    14 //判断object的thread,也就是object所在的thread,不等于当前的线程就返回了
    15 
    16         qWarning("QObject::startTimer:timers cannot be started from another thread");
    17 
    18         return;
    19 
    20     }
    21 
    22  
    23 
    24   。。。。。
    25 
    26  
    27 
    28 }

    3.    你必须保证在线程中创建的对象要在线程销毁前delete。这很容易做到,只要是在run()函数栈里创建的对象就行。

    尽管 QObject 是可重入的,但是GUI类,特别是QWidget 和它的子类都是不可重入的。它们只能在主线程中用。就如前面提到的, QCoreApplication::exec()必须从主线程进行调用。

    Per-Thread Event Loop
    每个线程都有自己的事件循环。起始的线程用QCoreApplication::exec()开启事件循环。其他的线程用QThread::exec()开始事件循环。与 QCoreApplication一样, QThread也提供了 exit(int) 函数 和 quit() 槽函数。

    线程里的事件循环,使得可以在线程里使用需要事件循环的非GUI类,例如(QTimer, QTcpSocket, and QProcess).。也可以把任意的线程的信号连接到特定线程的槽。

    QObject实例存在于创建实例的线程中,发送给实例事件也是有线程的事件循环实现的。可以用 QObject::thread().获取对象存活于哪个线程。

     1 MyThread::MyThread(QObject*parent /* = NULL */):QThread(parent)
     2 
     3 {
     4        m_pTimer = newQTimer(this);
     5 
     6        qDebug()<<"MyThreadobject currentThreadId :"<<QThread::currentThread();
     7 
     8        QObject obj1;
     9 
    10        obj1.thread();
    11 
    12        qDebug()<<"obj1live in the thread  :"<<obj1.thread();
    13 
    14        connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));
    15 
    16        //QThread::start();
    17 
    18 }
    19 
    20  
    21 
    22  
    23 
    24 void MyThread::run()
    25 
    26 {
    27        QObject obj2;
    28 
    29        obj2.thread();
    30 
    31        qDebug()<<"button2live in the thread  :"<<obj2.thread();
    32 
    33        //m_pTimer->start(500);
    34 
    35        qDebug()<<"run()  currentThreadId : "<<QThread::currentThread();
    36 
    37        qDebug( "finish!");
    38 
    39 }

    这个再一次说明了,对象所处的线程就是创建它的线程。

    注意:对于那些在QApplication之前创建的对象,QObject::thread() 返回0。这意味着,主线程只处理发送给那些对象的事件,那些没有thread的对象是不做任何的事件处理的。使用QObject::moveToThread()函数可以改变对象及其子对象的线程关联度,说白了就是把对象从当前的线程移到另外的线程里。但是如果一个对象已经有了parent,那是不能move了。

    调用delete删除处于另外一个线程的QObject对象是不安全的。除非你能保证对象当前不是在进行事件处理。应该用QObject::deleteLater()替代,并且将发出一个DeferredDelete事件,这个事件会最终会被对象的线程的时间循环所捕获。

    如果没有时间循环,就不会有事件传递给对象。例如,如果你在一个线程中创建了一个QTimer对象,但是不调用exec(),,那么QTimer永远不会发出timeout()信号,调用eleteLater() 也不起作用。

     1 void MyThread::run()
     2 
     3 {
     4        m_pTimer = newQTimer();
     5 
     6        m_pTimer->start(500);
     7 
     8       
     9 
    10        connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));
    11 
    12        qDebug()<<"run()  currentThreadId : "<<QThread::currentThread();
    13 
    14        this->exec();
    15 
    16        //qDebug("finish!" );
    17 
    18 }
    19 
    20 void MyThread::timeOutSlot()
    21 
    22 {
    23        qDebug()<<"timer  timeout ";
    24 
    25        //m_pTimer->stop();
    26 
    27 }

    这时候是可以调用timeOutSlot()的。

     1 void MyThread::run()
     2 
     3 {
     4        m_pTimer = newQTimer();
     5 
     6        m_pTimer->start(500);
     7 
     8       
     9 
    10        connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));
    11 
    12        qDebug()<<"run()  currentThreadId : "<<QThread::currentThread();
    13 
    14        //this->exec();
    15 
    16        //qDebug("finish!" );
    17 
    18 }

    如果注释//this->exec();,timeOutSlot()将不会被调用。

    还有一点要注意的:QTimer对象也不能在另外的线程stop的。如果把timeOutSlot里的m_pTimer->stop();取消注释。会看到一行输出:QObject::killTimer: timers cannot be stopped fromanother thread

    源码中:

     1 bool QEventDispatcherWin32::unregisterTimer(int timerId)
     2 
     3 {
     4     if (timerId< 1) {
     5         qWarning("QEventDispatcherWin32::unregisterTimer:invalid argument");
     6 
     7         return false;
     8 
     9     }
    10 
    11 QThread *currentThread = QThread::currentThread();
    12 
    13 //判断timer所处的线程与当前的线程是否一致。
    14 
    15 if(thread() != currentThread)
    16 
    17  {
    18         qWarning("QObject::killTimer:timers cannot be stopped from another thread");
    19 
    20         return false;
    21 
    22     }
    23 
    24  
    25 
    26   。。。。
    27 
    28 }

    你可以用QCoreApplication::postEvent()函数在任意时间给任意线程中的任意对象发送事件。事件自动被创建object的线程的事件循环分发。所以的线程都支持事件过滤器,唯一的限制就是,监视对象必须与被监视对象处于同一个线程。同样的,QCoreApplication::sendEvent() 只能用来给与调用QCoreApplication::sendEvent() 函数处于同一个线程的对象发送事件。说白了就是,QCoreApplication::sendEvent() 不能给处于另外线程的对象发送事件。

    Accessing QObjectSubclasses from Other Threads
    QObject 和它所有的子类都不是线程安全的。这包含了整个事件发送系统,需要记住的很重要的一点是:事件循环可能正在给一个对象发送一个事件,同时你可能从别的线程访问该对象。

    如果你调用了一个不是出于当前线程QObject 子类对象的一个函数,而此时对象可能接收一个事件,你必须用一个mutex保护对象的内在的数据。否则,可能引起程序崩溃或者未定义的行为。

    与其他的对象一样,QThread对象存活于创建对象的线程中,而不是存在于QThread::run() 线程。这点在前面讲到了。在自定义 QThread子类中提供slot函数是不安全的,除非你用一个mutex保护了成员变量。然而,你可以在实现的 QThread::run() 里发出信号,因为信号发送是线程安全的。

    Signals and Slots AcrossThreads
    Qt支持了几种信号--槽的连接方式:

    1.     Auto Connection (默认):如果如果信号的发送方与接收方是处于同一个线程,这个连接就是 Direct Connection,否则就跟 Queued Connection一样。

    2.    Direct Connection :当信号发出之后,槽会立即被调用。槽函数是在信号发送方的线程中运行的,不需要接收方的线程。

    3.    Queued Connection:当控制权回到接收方线程时调用槽函数。槽函数是在接收方的线程中运行的。

    4.    Blocking Queued Connection :调用方式跟 Queued Connection一样,区别在于,当前线程会被阻塞直到槽函数返回。

    5.    Unique Connection :这种方式跟 Auto Connection一样,但是只有当不存在一个相同的连接时才会创建一个连接。如果已经存在相同的连接,则不会创建连接,connect()返回false。

    可以在connect()添加参数指定连接类型。需要注意的一点是:如果信号发送方和接收方处于不同的线程,而且接收方线程运行着一个事件循环,此时用Direct Connection是不安全,原因跟调用一个对象的函数,而这个对象处于另外的线程,那样的调用是不安全。

    QObject::connect() 本身是线程安全的。

    下面通过结果例子验证一下:

     1 class Receiver:publicQObject
     2 
     3 {
     4        Q_OBJECT
     5 
     6 public:
     7 
     8  
     9 
    10        voidsendmes()
    11 
    12        {
    13               emitemitSignal("emit message from A  To B");
    14 
    15        }
    16 
    17        Receiver()
    18 
    19        {
    20  
    21 
    22        }
    23 
    24 protected slots:
    25 
    26        voidmessageSlot(QString mes)
    27 
    28        {
    29               qDebug()<<mes;
    30 
    31        }
    32 
    33 signals:
    34 
    35        voidemitSignal(QString mes);
    36 
    37  
    38 
    39 private:
    40 
    41 };
    42 
    43 int main(int argc, char *argv[])
    44 
    45 {
    46        QApplication a(argc, argv);
    47 
    48        Receiver objA,objB;
    49 
    50       
    51 
    52        QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)));
    53 
    54        qDebug()<<"beforeemitsignal ";
    55 
    56        objA.sendmes();
    57 
    58        qDebug()<<"afteremitsignal ";
    59 
    60        returna.exec();
    61 
    62 }

    objA,objB;出于同一个线程,所以connect的连接类型是Direct Connection

    由输出我们可以看出执行顺序,

     

     如果我们写了两句连接:

    1 QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)));
    2 QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)));

    就会相应的有两句消息输出:

    如果指定了连接类型Qt::UniqueConnection ,就会只有一句消息输出了。

    1 QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)),Qt::UniqueConnection );
    2 QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)),Qt::UniqueConnection);

     1 int main(int argc, char *argv[])
     2 
     3 {
     4        QApplication a(argc, argv);
     5 
     6        QThread *thread = new QThread;
     7 
     8        thread->start();
     9 
    10        Receiver objA,objB;
    11 
    12        objB.moveToThread(thread);
    13 
    14        QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)) );
    15 
    16        qDebug()<<"beforeemitsignal ";
    17 
    18        objA.sendmes();
    19 
    20        qDebug()<<"afteremitsignal ";
    21 
    22        returna.exec();
    23 
    24 }

    如果我们把objB放到另外的线程,connect的连接类型应该是Queued Connection 。

     

     1 int main(int argc, char *argv[])
     2 
     3 {
     4        QApplication a(argc, argv);
     5 
     6        QThread *thread = new QThread;
     7 
     8        thread->start();
     9 
    10        Receiver objA,objB;
    11 
    12        objB.moveToThread(thread);
    13 
    14 QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)) ,Qt::BlockingQueuedConnection);
    15 
    16        qDebug()<<"beforeemitsignal ";
    17 
    18        objA.sendmes();
    19 
    20        qDebug()<<"afteremitsignal ";
    21 
    22        returna.exec();
    23 
    24 }

    显示的指定连接类型为Qt::BlockingQueuedConnection,则输出为:

     

  • 相关阅读:
    Tkinter之Label部件
    Tkinter编码风格
    GUI之tkinter视窗设计模块
    绘制函数图像
    backbone学习总结(一)
    实习两个月,写在辞职的今天
    Spark SQL 编程初级实践2- 编程实现利用 DataFrame 读写 MySQL 的数据
    Spark SQL 编程初级实践1-Spark SQL 基本操作
    spark创建DataFrame的几种方式
    python-with open() as file相关参数以及常用打开方式
  • 原文地址:https://www.cnblogs.com/ybqjymy/p/14921757.html
Copyright © 2011-2022 走看看