QThread 与 QObject的关系

QThread 与 QObject的关系

Threads and QObjects

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

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

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

MyThread ::MyThread(QObject *parent /* = NULL */ ):QThread(parent)

{

       qDebug()<< "MyThreadobject currentThreadId :" <<QThread::currentThreadId();

}

void MyThread::run()

{

       qDebug()<< "run()  currentThreadId : " <<QThread::currentThreadId();

}

 

int main( int argc, char *argv[])

{

       QApplication a(argc, argv);

       MyThread thread;

       qDebug()<< "mainThread : " <<QThread::currentThreadId();

       thread.start();

       return a.exec();

}

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

QObject Reentrancy

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

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

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

我们可以验证一下:

class MyThread : public QThread

{

       Q_OBJECT

public :

       MyThread(QObject *parent = NULL);

       ~MyThread();

public slots :

       void timeOutSlot();

protected :

       void run();

       QTimer *m_pTimer;

};

MyThread::MyThread(QObject*parent /* = NULL */ ):QThread(parent)

{

       m_pTimer = new QTimer( this );

       qDebug()<< "MyThreadobject currentThreadId :" <<QThread::currentThreadId();

       connect(m_pTimer, SIGNAL (timeout()), this , SLOT (timeOutSlot()));

}

void MyThread::timeOutSlot()

{

       qDebug()<< "timer  timeout " ;

}

MyThread::~MyThread()

{

}

void MyThread::run()

{

       m_pTimer->start(500);

       qDebug()<< "run()  currentThreadId : " <<QThread::currentThreadId();

       qDebug( "finish!" );

}

 int main( int argc, char *argv[])

{

       QApplication a(argc, argv);

       MyThread thread;

       qDebug()<< "mainThread : " <<QThread::currentThreadId();

       thread.start();

       return a.exec();

}

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

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

void QEventDispatcherWin32::registerTimer( int timerId, int interval, QObject *object)

{

    if (timerId< 1 || interval < 0 || !object) {

        qWarning( "QEventDispatcherWin32::registerTimer:invalid arguments" );

        return ;

}

  else if (object->thread() != thread() || thread() != QThread::currentThread())

{

//判断object的thread,也就是object所在的thread,不等于当前的线程就返回了

        qWarning( "QObject::startTimer:timers cannot be started from another thread" );

         return ;

    }

  。。。。。

}

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 (). 获取对象存活于哪个线程。

MyThread::MyThread(QObject*parent /* = NULL */ ):QThread(parent)

{

       m_pTimer = new QTimer( this );

       qDebug()<< "MyThreadobject currentThreadId :" <<QThread::currentThread();

       QObject obj1;

       obj1.thread();

       qDebug()<< "obj1live in the thread  :" <<obj1.thread();

       connect(m_pTimer, SIGNAL (timeout()), this , SLOT (timeOutSlot()));

       //QThread::start();

}

void MyThread::run()

{

       QObject obj2;

       obj2.thread();

       qDebug()<< "button2live in the thread  :" <<obj2.thread();

       //m_pTimer->start(500);

       qDebug()<< "run()  currentThreadId : " <<QThread::currentThread();

       qDebug( "finish!" );

}

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

 

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

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

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

void MyThread::run()

{

       m_pTimer = new QTimer();

       m_pTimer->start(500);

       connect(m_pTimer, SIGNAL (timeout()), this , SLOT (timeOutSlot()));

       qDebug()<< "run()  currentThreadId : " <<QThread::currentThread();

       this ->exec();

       //qDebug("finish!" );

}

void MyThread::timeOutSlot()

{

       qDebug()<< "timer  timeout " ;

       //m_pTimer->stop();

}

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

  

void MyThread::run()

{

       m_pTimer = new QTimer();

       m_pTimer->start(500);

       connect(m_pTimer, SIGNAL (timeout()), this , SLOT (timeOutSlot()));

       qDebug()<< "run()  currentThreadId : " <<QThread::currentThread();

       //this->exec();

       //qDebug("finish!" );

}

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

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

源码中:

bool QEventDispatcherWin32::unregisterTimer( int timerId)

{

    if (timerId< 1) {

        qWarning( "QEventDispatcherWin32::unregisterTimer:invalid argument" );

        return false ;

    }

QThread *currentThread = QThread::currentThread();

//判断timer所处的线程与当前的线程是否一致。

if (thread() != currentThread)

 {

        qWarning( "QObject::killTimer:timers cannot be stopped from another thread" );

        return false ;

    }

  。。。。

}

你可以用 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 ()  本身是线程安全的。

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

 

class Receiver: public QObject

{

       Q_OBJECT

public :

       void sendmes()

       {

              emit emitSignal( "emit message from A  To B" );

       }

       Receiver()

       {

       }

protected slots :

       void messageSlot(QString mes)

       {

              qDebug()<<mes;

       }

signals :

       void emitSignal(QString mes);

private :

};

int main( int argc, char *argv[])

{

       QApplication a(argc, argv);

       Receiver objA,objB;

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

       qDebug()<< "beforeemitsignal " ;

       objA.sendmes();

       qDebug()<< "afteremitsignal " ;

       return a.exec();

}

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

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

  

如果我们写了两句连接:

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

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

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

 

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

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

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

  

int main( int argc, char *argv[])

{

       QApplication a(argc, argv);

       QThread *thread = new QThread;

       thread->start();

       Receiver objA,objB;

       objB.moveToThread(thread);

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

       qDebug()<< "beforeemitsignal " ;

       objA.sendmes();

       qDebug()<< "afteremitsignal " ;

       return a.exec();

}

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

  

int main( int argc, char *argv[])

{

       QApplication a(argc, argv);

       QThread *thread = new QThread;

       thread->start();

       Receiver objA,objB;

       objB.moveToThread(thread);

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

       qDebug()<< "beforeemitsignal " ;

       objA.sendmes();

       qDebug()<< "afteremitsignal " ;

       return a.exec();

}

 

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

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值