本文主要内容:

在任务一中,用 四 种方式实现:点击界面按钮,开线程运行一段程序,结果显示在一个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的信号
  1. #include <QtCore>
  2. #include <QtGui>
  3.  
  4. class Thread:public QThread
  5. {
  6. Q_OBJECT
  7. public:
  8. Thread(){}
  9. public slots:
  10. void slot1(int v)
  11. {
  12. qDebug()<<"slots1: "<<currentThreadId();
  13. emit sig1(QString::number(v*v*v));
  14. }
  15. signals:
  16. void sig1(const QString& t);
  17. protected:
  18. void run()
  19. {
  20. qDebug()<<"thread: "<<currentThreadId();
  21. exec();
  22. }
  23. };
  24.  
  25. class Widget:public QWidget
  26. {
  27. Q_OBJECT
  28. public:
  29. Widget():m_label(new QLabel), m_button(new QPushButton("Button")), m_thread(new Thread)
  30. {
  31. QVBoxLayout * vbox = new QVBoxLayout(this);
  32. vbox->addWidget(m_label);
  33. vbox->addWidget(m_button);
  34. setLayout(vbox);
  35. connect(m_button,SIGNAL(clicked()),this,SLOT(onButtonClicked()));
  36. connect(this,SIGNAL(clicked(int)),m_thread,SLOT(slot1(int)));
  37. connect(m_thread,SIGNAL(sig1(QString)),m_label,SLOT(setText(QString)));
  38. m_thread->start();
  39. }
  40. signals:
  41. void clicked(int v);
  42. private slots:
  43. void onButtonClicked()
  44. {
  45. static int v = 0;
  46. emit clicked(v);
  47. v++;
  48. }
  49. private:
  50. QLabel * m_label;
  51. QPushButton * m_button;
  52. Thread * m_thread;
  53. };
  54.  
  55. #include "main.moc"
  56. int main(int argc, char** argv)
  57. {
  58. QApplication app(argc, argv);
  59. qDebug()<<"main: "<<QThread::currentThreadId();
  60. Widget w;
  61. w.show();
  62. return app.exec();
  63. }

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

  1. main: 3055777552
  2. thread: 3024481136
  3. slots1: 3055777552
  4. slots1: 3055777552
  5. slots1: 3055777552
  6. ...

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

尝试二

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

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

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

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

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

  1. class Thread:public QThread
  2. {
  3. Q_OBJECT public:
  4. Thread(){}
  5. public slots:
  6. void slot1(int v)
  7. {
  8. qDebug()<<"slots1: "<<currentThreadId();
  9. m_mutex.lock();
  10. m_vals.enqueue(v);
  11. m_mutex.unlock();
  12. }
  13. signals:
  14. void sig1(const QString& t);
  15. protected:
  16. void run()
  17. {
  18. qDebug()<<"thread: "<<currentThreadId();
  19. while(1) {
  20. m_mutex.lock();
  21. if (!m_vals.isEmpty()){
  22. int v = m_vals.dequeue();
  23. emit sig1(QString::number(v*v*v));
  24. }
  25. m_mutex.unlock();
  26. }
  27. }
  28. private:
  29. QQueue<int> m_vals;
  30. QMutex m_mutex;
  31. };

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

尝试三

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

  1. class Thread:public QThread
  2. {
  3. Q_OBJECT public:
  4. Thread(){ moveToThread(this); }
  5. ...

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

  1. main: 3056785168
  2. thread: 3024444272
  3. slots1: 3024444272
  4. slots1: 3024444272
  5. ...

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

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

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

尝试四

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

  • 我们不子类话QThread了,我们只需要子类话一个QObject,然后将其move到QThread就行了,看代码:
  • 不用子类化QThrad了,我们只需要子类话一个 QObject,需要在次线程中工作的代码,直接放到它的槽中
  1. class Worker:public QObject
  2. {
  3. Q_OBJECT
  4. public:
  5. Worker(){}
  6. public slots:
  7. void slot1(int v)
  8. {
  9. qDebug()<<"slots1: "<<QThread::currentThreadId();
  10. emit sig1(QString::number(v*v*v));
  11. }
  12. signals:
  13. void sig1(const QString& t);
  14. };
  • 因为没有Thread类了,只有Worker类,Widget代码需做点改动
  1. class Widget:public QWidget
  2. {
  3. Q_OBJECT public:
  4. Widget():m_label(new QLabel), m_button(new QPushButton("Button")), m_worker(new Worker)
  5. {
  6. QVBoxLayout * vbox = new QVBoxLayout(this);
  7. vbox->addWidget(m_label);
  8. vbox->addWidget(m_button);
  9. setLayout(vbox);
  10. QThread * thread = new QThread(this);
  11. m_worker->moveToThread(thread);
  12. connect(m_button,SIGNAL(clicked()),this,SLOT(onButtonClicked()));
  13. connect(this,SIGNAL(clicked(int)),m_worker,SLOT(slot1(int)));
  14. connect(m_worker,SIGNAL(sig1(QString)),m_label,SLOT(setText(QString)));
  15. thread->start();
  16. }
  17. signals:
  18. void clicked(int v);
  19. private slots:
  20. void onButtonClicked()
  21. {
  22. static int v = 0;
  23. emit clicked(v);
  24. v++;
  25. }
  26. private:
  27. QLabel * m_label;
  28. QPushButton * m_button;
  29. Worker * m_worker;
  30. };

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

控制台输出结果如下

  1. main: 3056961296
  2. slots1: 3024616304
  3. slots1: 3024616304
  4. ....

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

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

QThread与QWidget使用的更多相关文章

  1. QT 线程的使用(继承QThread)

    对于多线程而言,要注意资源的同步和互斥问题,但对于单独的一个线程,则只需要对它的run方法进行重写. 下面实现了一个简单的线程 widget.h文件 #ifndef WIDGET_H #define ...

  2. 2、QT分析之QPushButton的初始化

    原文地址:http://blog.163.com/net_worm/blog/static/127702419201001003326522/ 在简单的QT程序的第二行,声明了一个QPushButto ...

  3. QT分析之QPushButton的初始化

    原文地址:http://blog.163.com/net_worm/blog/static/127702419201001003326522/ 在简单的QT程序的第二行,声明了一个QPushButto ...

  4. qt之线程

    第一种创建: mythread1.h: #ifndef MYTHREAD_H #define MYTHREAD_H #include<QThread> #include<QDebug ...

  5. QWidget中结束QThread线程

    QThread安全结束 protected: void closeEvent(QCloseEvent *event); void closeEvent(QCloseEvent *event) { th ...

  6. Qt QThread 多线程使用

    一.继承QThread 使用方法 1.创建个继承QThread的类. #ifndef MYTHREAD_H #define MYTHREAD_H #include <QObject> #i ...

  7. Pyqt Smtplib实现Qthread多线程发送邮件

    一. smtplib 的介绍 smtplib.SMTP([host[, port[, local_hostname[, timeout]]]])   SMTP类构造函数,表示与SMTP服务器之间的连接 ...

  8. QThread

    #ifndef MYTHREAD_H #define MYTHREAD_H #include<QThread> class MyThread : public QThread { Q_OB ...

  9. Qt之QThread(深入理解)

    简述 为了让程序尽快响应用户操作,在开发应用程序时经常会使用到线程.对于耗时操作如果不使用线程,UI界面将会长时间处于停滞状态,这种情况是用户非常不愿意看到的,我们可以用线程来解决这个问题. 前面,已 ...

随机推荐

  1. 通过VMware Tools 将主机windows的目录共享给linux虚拟机

    之前有写过 本地虚拟机挂载windows共享目录搭建开发环境 这篇,里面讲通过使用samba来实现网络共享 最近发现其实完全不用这么麻烦,VMware tools就可以帮助我们轻松的共享文件夹 这里引 ...

  2. Spark及其应用场景初探

    最近老大让用Spark做一个ETL项目,搭建了一套只有三个结点Standalone模式的Spark集群做测试,基础数据量大概8000W左右.看了官方文档,Spark确实在Map-Reduce上提升了很 ...

  3. 桶排序-Swift

    import Foundation let b:Array = [5,2,3,1,8] var a:NSMutableArray = [] for var i in 0 ..< 11 { a[i ...

  4. 移动端和web端前端UI库—Frozen UI、WeUI、SUI Mobile

    web http://www.pintuer.com/ 拼图 http://www.h-ui.net/ http://www.layui.com/  很厉害的一个个人产品 http://amazeui ...

  5. 轻松学习Linux之入门篇

    1.Linux概述: 2.Linux优点 3.linux历史待上传 4.linux部分发行版 5.linux政府扶持 本文出自 "李晨光原创技术博客" 博客,谢绝转载!

  6. [札记]IL经典指令解析之方法调度

    call.callvirt和calli指令用于完成方法调用,有何区别呢? 1)call使用静态调度,也就是根据引用类型的静态类型来调度方法.call指令根据引用变量的类型来调用方法,因此通常用于调用非 ...

  7. keil中如何得知所编译程序所占空间大小?

    keil编译后出现Program Size: data=21.0 xdata=0 code=2231. 这表明 data= 21.0  数据储存器内部RAM占用21字节, xdata=0     数据 ...

  8. LeetCode292:Nim Game

    You are playing the following Nim Game with your friend: There is a heap of stones on the table, eac ...

  9. 用 const 限定类的成员函数

    类的成员函数后面加 const,表明这个函数不会对这个类对象的数据成员(准确地说是非静态数据成员)作任何改变. 在设计类的时候,一个原则就是对于不改变数据成员的成员函数都要在后面加 const,而对于 ...

  10. 可以binidng属性的属性【项目】

    1:binding后台bool[]数据以及后台ObservableCollection数据 分别见下面xaml的Visibility和Text的Binding public bool[] Rubber ...