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

怎么正确使用QThread

历史回顾

很久之前,在使用QThread过程中,继承QThread和重载它的run()函数是唯一推荐的方式。这种用法是很很直观和简单的。但是,当在工作线程中使用SLOTS和Qtevent 循环时,一些用户往往会犯一些错误。所以,作为Qt的核心成员之一的Bradley T. Hughes,++推荐大家大家使用 QObject::moveToThread 把工作对象加入线程中++。不过,一些用户仍然对之前的用法进行了改革。所以,前Qt核心开发人员Olivier Goffart,告诉那些指着与继承Qthhread的用户:你并没有用错。最终,我们可以在QTread的人当中看到两种不同的用法。

QThread::run() 是现成的入口点

从Qt官方文档,我们可以看出:

A QThread instance represents a thread and provides the means to start() a thread, which will then execute the reimplementation of QThread::run(). The run() implementation is for a thread what the main() entry point is for the application.

QThread::run() 是现成的入口点

用法1-0

继承Qthread然后重载run()

例如:

#include <QtCore>

class Thread : public QThread
{
private:
    void run()
    {
        qDebug()<<"From worker thread: "<<currentThreadId();
    }
};

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

    Thread t;
    QObject::connect(&t, SIGNAL(finished()), &a, SLOT(quit()));

    t.start();
    return a.exec();
}

输出如下:

From main thread:  0x15a8 
From worker thread:  0x128c

用法1-1

因为QThread::run()是线程的入口,所以,没在在run()函数里调用的所有代码,都不会再工作线程里面执行。
在下面的例子中,成员变量m_stop将会被 stop() and run()访问和存储。假设前者将在主线程中执行,而后者则在工作线程中执行,则需要使用互斥锁或其他措施。

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

class Thread : public QThread
{
    Q_OBJECT

public:
    Thread():m_stop(false)
    {}

public slots:
    void stop()
    {
        qDebug()<<"Thread::stop called from main thread: "<<currentThreadId();
        QMutexLocker locker(&m_mutex);
        m_stop=true;
    }

private:
    QMutex m_mutex;
    bool m_stop;

    void run()
    {
        qDebug()<<"From worker thread: "<<currentThreadId();
        while (1) {
            {
            QMutexLocker locker(&m_mutex);
            if (m_stop) break;
            }
            msleep(10);
        }
    }
};

#include "main.moc"
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    qDebug()<<"From main thread: "<<QThread::currentThreadId();
    QPushButton btn("Stop Thread");
    Thread t;

    QObject::connect(&btn, SIGNAL(clicked()), &t, SLOT(stop()));
    QObject::connect(&t, SIGNAL(finished()), &a, SLOT(quit()));

    t.start();
    btn.show();
    return a.exec();
}

输出如下:

From main thread:  0x13a8 
From worker thread:  0xab8 
Thread::stop called from main thread:  0x13a8

可以看到主线程执行了 Thread::stop()

用法1-2(错误用法)

尽管上面的例子很好理解,但是,在工作线程加入了事件系统和队列连接就不那么直观了。

例如,如果我们想在工作线程中加入一些周期性的东西要怎么办?

  • 在Thread::run()创建QTimer
  • 吧timeout()的信号连接到THread的槽函数

codes

#include <QtCore>

class Thread : public QThread
{
    Q_OBJECT
private slots:
    void onTimeout()
    {
        qDebug()<<"Thread::onTimeout get called from? : "<<QThread::currentThreadId();
    }

private:
    void run()
    {
        qDebug()<<"From worker thread: "<<currentThreadId();
        QTimer timer;
        connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
        timer.start(1000);

        exec();
    }
};

#include "main.moc"

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

    Thread t;
    t.start();

    return a.exec();
}

粗略看上去,好像并没有什么不妥,当线程开始执行,我们设置了一个定时器,连接onTimeout()和槽函数。但是我们能够得到期望的结果吗?

事实上,结果如下:

From main thread:  0x13a4 
From worker thread:  0x1330 
Thread::onTimeout get called from?:  0x13a4 
Thread::onTimeout get called from?:  0x13a4 
Thread::onTimeout get called from?:  0x13a4

可以看得出,定时执行的操作是在主线程中执行的。很有趣,不是吗?
我们会在后续继续讨论这个执行结果的原因。

so 怎么解决这个问题?

为了让才函数在工作线程中工作,有些人通过Qt::DirectConnection去连接

connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()), Qt::DirectConnection);

另一些使用者使用如下方法:


moveToThread(this)

恩,这两个都能得到我们想要的结果。但是第二种方法是错的!!!!!!!!

即使可以得到期望结果。这是很混乱的!这不是QThread的涉及用法。QThread里所有的函数只能被创建线程所调用,而不能在启动线程使用!

事实上,根据上述观点,第一种工作方案也是错的,Thread object的成员函数onTimeout() 也被创建函数调用了。

全部用错了,怎么办?手动黑人脸.gif

用法3

由于QThread object 没有涉及能够从工作线程点用的成员。所以我们想使用slot,就必须传建一个独立的工作对象。

#include <QtCore>

class Worker : public QObject
{
    Q_OBJECT
private slots:
    void onTimeout()
    {
        qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId();
    }
};

class Thread : public QThread
{
    Q_OBJECT

private:
    void run()
    {
        qDebug()<<"From work thread: "<<currentThreadId();
        QTimer timer;
        Worker worker;
        connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
        timer.start(1000);

        exec();
    }
};

#include "main.moc"

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

    Thread t;
    t.start();

    return a.exec();
}

运行结果如下:

From main thread:  0x810 
From work thread:  0xfac 
Worker::onTimeout get called from?:  0xfac 
Worker::onTimeout get called from?:  0xfac 
Worker::onTimeout get called from?:  0xfac

问题解决!

虽然运行很完美,但是,我们可以及注意到工作线程中仍然使用了时间循环 QThread::exec(),其他的与QThread 自身没什么关系了。

因此,因此,我们是否可以将对象创建从QThread::run()中移出,同时,它们的槽仍将被QThread调用::运行?

用法2-0

QThread::exec()默认被QThread::run() 调用,如果我们仅仅想要使用他,我们没必要再继承QThread 了

  • 创建工作对象
  • 连接信号与槽函数
  • 把工作对象移入子线程
  • 启动线程

codes:

include <QtCore>

class Worker : public QObject
{
    Q_OBJECT
private slots:
    void onTimeout()
    {
        qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId();
    }
};

#include "main.moc"

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

    QThread t;
    QTimer timer;
    Worker worker;

    QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
    timer.start(1000);

    timer.moveToThread(&t);
    worker.moveToThread(&t);

    t.start();

    return a.exec();
}

运行结果如下:

From main thread:  0x1310 
Worker::onTimeout get called from?:  0x121c 
Worker::onTimeout get called from?:  0x121c 
Worker::onTimeout get called from?:  0x121c

与所期望的一样,槽函数并没有在主线程运行。

在这个例子中, QTimer and Worker 都被放入到了子线程,事实上,把Qtimer放入子线程是不必要的。

用法2-1

tips:仅仅改变了QTimer

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

    QThread t;
    QTimer timer;
    Worker worker;

    QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
    timer.start(1000);

//    timer.moveToThread(&t);
    worker.moveToThread(&t);

    t.start();

    return a.exec();
}

与前者的不同之处在于:
在上个例子中,
- 信号timeout()在子线程中被通知
- timer和worker运行在同一个线程,他们的连接方式是直接连接(direct connection)
- 通知在同一个线程传递

然而,在本例中:

  • 信号timeout()在主线程发送通知(emit)
  • timer和worker在不同线程,通过queued connection方式连接
  • 槽函数在子线程调用

多亏这个叫做queued connections的机制,他实现了 不同线程间信号与槽函数的安全连接。如果所有的跨线程信息传递都被设计成queued connections,那么开发者就不在需要像QMutex 这样的多线程的预防措施了。

总结:

  • Subclass QThread and reimplement its run() function is intuitive and there are still many perfectly valid reasons to subclass QThread, but when event loop is used in worker thread, it’s not easy to do it in the right way.
  • Use worker objects by moving them to the thread is easy to use when event loop exists, as it has hidden the details of event loop and queued connection.

引用:
- http://blog.debao.me/2013/08/how-to-use-qthread-in-the-right-way-part-1/?utm_source=qq&utm_medium=social

猜你喜欢

转载自blog.csdn.net/dyingstraw/article/details/79085746
今日推荐