首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

QThread与QWidget运用

2012-09-22 
QThread与QWidget使用?原文链接:http://hi.baidu.com/cyclone/blog/item/65f3f603294f2e783812bb51.html注

QThread与QWidget使用

?

原文链接:http://hi.baidu.com/cyclone/blog/item/65f3f603294f2e783812bb51.html

注意:请优先考虑Qt 线程基础(QThread、QtConcurrent等)

dbzhang800 2011.06.18

?

本文主要内容:

?

在任务一中,用 四 种方式实现:点击界面按钮,开线程运行一段程序,结果显示在一个Label上。
1. 用不正确的方式得到看似正确的结果
2. 用Qt Manual 和 例子中使用的方法
3. 用一种好用但被Qt开发人员批判的方法
4. 用一种被开发人员强烈推荐,但Qt Manual和例子中只字未提的方法
  • 为了简单起见,本文只讲如何做及其结果是什么,而不讲其原因是什么(估计大家对原因也不会感兴趣,详见:?QThread 使用探讨? 和?QThread使用方法)。

  • 本文只考虑两个线程(即主线程和一个次线程)的情况。

    QWidget
    • QWidget及其派生类均 不能在次线程中使用或创建

      Manual 中的原话:

      • The GUI classes, notably QWidget and all its subclasses, are not reentrant. They can only be used from the main thread.
      • 因为不允许,所以尝试这么做的,几乎很快都能回头。毕竟signals和slots用起来确实蛮方便
      • 但是,回头后,就理解和用对 QThread 了么?

        QThread

        概念一:QThread 对象本身所依附的线程 和它管理的线程不是同一个线程。

        • 前者是主线程
        • 后者是次线程

          概念二:你在QThread派生类中定义的槽在主线程而不是在次线程中执行的。

          • run 函数是线程的入口点,run内的代码才是在次线程中运行的代码

            概念三:除了Manual和Qt例子中给出的用法外,QThread有一种更容易且被推荐的使用方法:

            • QThread 应该被看做是操作系统线程的接口或控制点,而不应该包含需要在新线程中运行的代码
            • 需要运行的代码应该放到一个QObject的子类中,然后将该子类的对象moveToThread到新线程中。

              关于本文的例子
              • 为了代码简单,所有例子都是单一的源文件,保存为 main.cpp
                • 你从代码中包含的?#include“main.moc”应该能看出

                • 为了省几行代码,头文件都是直接包含 QtCore 和 QtGui。

                • 为了清楚告诉大家槽函数分别是在那个线程运行的,调用了几处 currentThreadId 函数
                  • main 函数中输出主线程IDqDebug()<<"main:?"<<QThread::currentThreadId();

                  • run 函数中输出次线程IDqDebug()<<"thread:?"<<currentThreadId();

                  • 槽函数中输出其在哪个线程中执行qDebug()<<"slots1:?"<<currentThreadId();

                  • 因为用了qDebug,所以你的pro文件内最好加上?CONFIG+=console

                  • 同样为了省代码,例子中未考虑线程如何正常结束的问题。

                    任务一

                    点击界面按钮,开线程运行一段程序,结果显示在一个Label上。

                    • 定义一个Widget,上面放置 QPushButton 和 QLabel
                    • 定义一个Thread,执行我们的代码,然后通知 Widget

                      第一次尝试

                      很容易想到方法,代码可以工作,结果正确。但 ... 未必和你想得一样

                      • Thread 中定义一个slot1函数,接受数据,计算其立方,然后将结果通过信号发出
                      • Widget 中按钮每点击一次,发出的数据加1,用label接受Thread的信号

                        ?

                        #include <QtCore> #include <QtGui>  class Thread:public QThread {     Q_OBJECT public:     Thread(){} public slots:     void slot1(int v)     {         qDebug()<<"slots1: "<<currentThreadId();         emit sig1(QString::number(v*v*v));     } signals:     void sig1(const QString& t); protected:     void run()     {         qDebug()<<"thread: "<<currentThreadId();         exec();     } };  class Widget:public QWidget {     Q_OBJECT public:     Widget():m_label(new QLabel), m_button(new QPushButton("Button")), m_thread(new Thread)     {         QVBoxLayout * vbox = new QVBoxLayout(this);         vbox->addWidget(m_label);         vbox->addWidget(m_button);         setLayout(vbox);          connect(m_button,SIGNAL(clicked()),this,SLOT(onButtonClicked()));         connect(this,SIGNAL(clicked(int)),m_thread,SLOT(slot1(int)));         connect(m_thread,SIGNAL(sig1(QString)),m_label,SLOT(setText(QString)));         m_thread->start();     } signals:     void clicked(int v); private slots:     void onButtonClicked()     {         static int v = 0;         emit clicked(v);         v++;     } private:     QLabel * m_label;     QPushButton * m_button;     Thread * m_thread; };  #include "main.moc" int main(int argc, char** argv) {     QApplication app(argc, argv);     qDebug()<<"main: "<<QThread::currentThreadId();     Widget w;     w.show();     return app.exec(); }

                        一切工作正常,但看看控制台输出呢?

                        main:  3055777552 thread:  3024481136  slots1:  3055777552  slots1:  3055777552  slots1:  3055777552  ...

                        这儿明确告诉你,slot1 是在主线程中执行的。

                        尝试二

                        我们试试 Qt Manual和 Qt 例子中采用的解决方案。

                        槽函数不是在主线程运行么,而run函数不是次线程么?那么我们就:

                        • 在槽函数中做个标记
                        • 在run函数中根据标记进行运行

                          这样以来,尽管槽函数在仍在主线程,但费时的计算代码都在次线程了。

                          对Thread的类的改造如下(程序其他部分和?尝试一?完全一样):

                          ?

                          class Thread:public QThread {     Q_OBJECT public:     Thread(){} public slots:     void slot1(int v)     {         qDebug()<<"slots1: "<<currentThreadId();         m_mutex.lock();         m_vals.enqueue(v);         m_mutex.unlock();     } signals:     void sig1(const QString& t); protected:     void run()     {         qDebug()<<"thread: "<<currentThreadId();         while(1) {             m_mutex.lock();             if (!m_vals.isEmpty()){                 int v = m_vals.dequeue();                 emit sig1(QString::number(v*v*v));             }             m_mutex.unlock();         }     } private:     QQueue<int> m_vals;     QMutex m_mutex; };

                          注意哦,因为 slot 函数在主线程中,而run函数在次线程中,所以二者需要 QMutex 实现对变量的安全访问。如果你认真看过Qt自带的例子,会发现它始终强调 QMutex 的使用。

                          尝试三

                          尝试二是"正统"的做法,但如过你用Google搜索过。那么你可能不会选择尝试二,而是会使用下面的方法(其他部分和尝试一?完全一样)

                          class Thread:public QThread {     Q_OBJECT public:     Thread(){ moveToThread(this); } ...

                          这样以来,slot函数确实是在次线程工作的,看看控制台输出

                          main:  3056785168  thread:  3024444272  slots1:  3024444272  slots1:  3024444272  ...

                          很有意思?不是么,一条 moveToThread(this),移动到自己。然后问题解决了。

                          • 因为前面说了,QThread 所依附线程 和 它管理的线程不是同一个。
                          • 这样,其实将自己所依附的线程改为自己所管理的线程了。

                            o(∩∩)o...哈哈,不要太高兴哦,这个方法看起来比较舒服,但是它是被官方人员强烈批判的用法

                            尝试四

                            终于到我想写的代码了,这是Qt线程的开发者建议的使用方式,但很可惜。直到目前(Qt4.7.0),手册和例子中对此都只字为提。

                            • 我们不子类话QThread了,我们只需要子类话一个QObject,然后将其move到QThread就行了,看代码:
                            • 不用子类化QThrad了,我们只需要子类话一个 QObject,需要在次线程中工作的代码,直接放到它的槽中

                              ?

                              class Worker:public QObject {     Q_OBJECT public:     Worker(){} public slots:     void slot1(int v)     {         qDebug()<<"slots1: "<<QThread::currentThreadId();         emit sig1(QString::number(v*v*v));     } signals:     void sig1(const QString& t); };
                              • 因为没有Thread类了,只有Worker类,Widget代码需做点改动

                                ?

                                class Widget:public QWidget {     Q_OBJECT public:     Widget():m_label(new QLabel), m_button(new QPushButton("Button")), m_worker(new Worker)     {         QVBoxLayout * vbox = new QVBoxLayout(this);         vbox->addWidget(m_label);         vbox->addWidget(m_button);         setLayout(vbox);          QThread * thread = new QThread(this);         m_worker->moveToThread(thread);          connect(m_button,SIGNAL(clicked()),this,SLOT(onButtonClicked()));         connect(this,SIGNAL(clicked(int)),m_worker,SLOT(slot1(int)));         connect(m_worker,SIGNAL(sig1(QString)),m_label,SLOT(setText(QString)));         thread->start();     } signals:     void clicked(int v); private slots:     void onButtonClicked()     {         static int v = 0;         emit clicked(v);         v++;     } private:     QLabel * m_label;     QPushButton * m_button;     Worker * m_worker; };

                                main 函数还是和尝试一完全一样

                                控制台输出结果如下

                                main:  3056961296  slots1:  3024616304  slots1:  3024616304  ....

                                一共两个线程,且二者id不同,说明slot在次线程中

                                恩。这篇文字似乎又不短了,看来任务二要另起一篇了。

                                • -- dbzhang800 于 20101023

热点排行