正确使用QThread的姿势(之二)

使用QThread有两种方式:
- 继承重载run()
- movetothread()

由于run()是工作线程的入口,所以前者比较容易理解。

着这片文章中,我尝试第二种方法的工作方式。

事件循环

作为一个事件驱动的编程框架,qt广泛使用事件循环。例如,一下函数背英语几乎所有的qt项目:

QCoreApplication::exec()
QDialog::exec()
QDrag::exec()
QMenu::exec()
QThread::exec()
...

他们每一个都会创建并运行一个QEventLoop 对象,拿QCoreApplication举例:

int QCoreApplication::exec()
{
//...
    QEventLoop eventLoop;
    int returnCode = eventLoop.exec();
//...
    return returnCode;
}

概念化描述如下:

int QEventLoop::exec(ProcessEventsFlags flags)
{
//...
    while (!d->exit) {
        while (!posted_event_queue_is_empty) {
            process_next_posted_event();
        }
    }
//...
}

每一个宪政拥有他自己的事件队列,特别是:事件队列属于线程,而不属于事件循环,他被运行在这个现场的所有事件循环共享。
当事件循环发现事件队列不为空,他会处理一个接一个处理这些事件。最终,目标对象的QObject::event()成员被调用。

看起来,没有一个例子作为佐证,我们很难理解时间系统是怎么工作,接下来,看一个而demo。

example

在这个例子中:
第一步:

  • 创建自定义事件new QEvent(QEvent::User)

  • 把事件post队列 QCoreApplication::postEvent()

然后,

  • 事件循环发现这个事件 QApplication::exec()

  • 事件循环调用Test::event()

#if QT_VERSION>=0x050000
#include <QtWidgets>
#else
#include <QtGui>
#endif

class Test : public QObject
{
    Q_OBJECT
protected:
    bool event(QEvent *evt)
    {
        if (evt->type() == QEvent::User) {
            qDebug()<<"Event received in thread"<<QThread::currentThread();
            return true;
        }
        return QObject::event(evt);
    }
};

class Button : public QPushButton
{
    Q_OBJECT
    Test *m_test;
public:
    Button(Test *test):QPushButton("Send Event"), m_test(test)
    {
        connect(this, SIGNAL(clicked()), SLOT(onClicked()));
    }

private slots:
    void onClicked()
    {
        QCoreApplication::postEvent(m_test, new QEvent(QEvent::User));
    }
};

#include "main.moc"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    qDebug()<<"From main thread: "<<QThread::currentThread();

    Test test;
    Button btn(&test);
    btn.show();

    return a.exec();
}

这个例子中,Test::event() 在主线程被调用。
如果我们想让他在工作线程中执行,要怎么办?

Thread Affinity(线程关联)

由于每个线程都有自己的事件队列,那么,在一个多线程应用中,就会有多个事件队列。问题来了,我们如何要把事件post到那个队列中?

看一下postEvent()的代码:

void QCoreApplication::postEvent(QObject *receiver, QEvent *event)
{
    QThreadData * volatile * pdata = &receiver->d_func()->threadData;
    QThreadData *data = *pdata;
    QMutexLocker locker(&data->postEventList.mutex);
    data->postEventList.addEvent(QPostEvent(receiver, event));
}

可以看出,事件队列包含在 receiver 的线程额属性值。这个线程被称为线程相关(这个对象所在的线程)。通常,这个线程就是创建对象的线程,但是,QObject::moveToThread() 可以改变之。

请注意,由于QMutex的存在,QCoreApplication::postEvent()是线程安全的。

现在, it’s easy to run the event process it worker thread instead of main thread.

举例:

在主函数添加三行代码:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    qDebug()<<"From main thread: "<<QThread::currentThread();

    Test test;
    QThread thread;               //new line
    test.moveToThread(&thread);   //new line
    thread.start();               //new line

    Button btn(&test);
    btn.show();

    return a.exec();
}

输出结果:

From main thread:  QThread(0x9e8100) 
Event received in thread QThread(0x13fed4) 
Event received in thread QThread(0x13fed4)

不添加那三行的输出结果如下:

From main thread:  QThread(0x9e8100) 
Event received in thread QThread(0x9e8100) 
Event received in thread QTh
read(0x9e8100)

Queued Connection

对于Queued Connection来说,当信号被通知,事件就会被post到事件队列。

QMetaCallEvent *ev = c->isSlotObject ?
        new QMetaCallEvent(c->slotObj, sender, signal, nargs, types, args) :
        new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal, nargs, types, args);
    QCoreApplication::postEvent(c->receiver, ev);

然后,这个事件就会被事件队列发现,最终在这个线程调用QObject::event()。

bool QObject::event(QEvent *e)
{
    switch (e->type()) {
    case QEvent::MetaCall:
        {
            QMetaCallEvent *mce = static_cast<QMetaCallEvent*>(e);

由于QCoreApplication::postEvent()线程安全,因此如果线程间用queued signal/slot connections的方式相互影响的时候,不需要考虑多线程安全防范措施。

引用:

http://blog.debao.me/2013/08/how-to-use-qthread-in-the-right-way-part-2/

猜你喜欢

转载自blog.csdn.net/dyingstraw/article/details/79087527