QThread与QWidget使用
本文主要内容:
在任务一中,用 四 种方式实现:点击界面按钮,开线程运行一段程序,结果显示在一个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在次线程中
恩。这篇文字似乎又不短了,看来任务二要另起一篇了。
QThread与QWidget使用的更多相关文章
- QT 线程的使用(继承QThread)
对于多线程而言,要注意资源的同步和互斥问题,但对于单独的一个线程,则只需要对它的run方法进行重写. 下面实现了一个简单的线程 widget.h文件 #ifndef WIDGET_H #define ...
- 2、QT分析之QPushButton的初始化
原文地址:http://blog.163.com/net_worm/blog/static/127702419201001003326522/ 在简单的QT程序的第二行,声明了一个QPushButto ...
- QT分析之QPushButton的初始化
原文地址:http://blog.163.com/net_worm/blog/static/127702419201001003326522/ 在简单的QT程序的第二行,声明了一个QPushButto ...
- qt之线程
第一种创建: mythread1.h: #ifndef MYTHREAD_H #define MYTHREAD_H #include<QThread> #include<QDebug ...
- QWidget中结束QThread线程
QThread安全结束 protected: void closeEvent(QCloseEvent *event); void closeEvent(QCloseEvent *event) { th ...
- Qt QThread 多线程使用
一.继承QThread 使用方法 1.创建个继承QThread的类. #ifndef MYTHREAD_H #define MYTHREAD_H #include <QObject> #i ...
- Pyqt Smtplib实现Qthread多线程发送邮件
一. smtplib 的介绍 smtplib.SMTP([host[, port[, local_hostname[, timeout]]]]) SMTP类构造函数,表示与SMTP服务器之间的连接 ...
- QThread
#ifndef MYTHREAD_H #define MYTHREAD_H #include<QThread> class MyThread : public QThread { Q_OB ...
- Qt之QThread(深入理解)
简述 为了让程序尽快响应用户操作,在开发应用程序时经常会使用到线程.对于耗时操作如果不使用线程,UI界面将会长时间处于停滞状态,这种情况是用户非常不愿意看到的,我们可以用线程来解决这个问题. 前面,已 ...
随机推荐
- 25个让人无法抗拒的HTML5网站设计实例
原文地址:http://www.goodfav.com/html5-website-designs-8272.html HTML5在其功能方面给网络市场带来了惊人的改进. HTML5是万维网联盟,在起 ...
- Python实现模拟登陆
大家经常会用Python进行数据挖掘的说,但是有些网站是需要登陆才能看到内容的,那怎么用Python实现模拟登陆呢?其实网路上关于这方面的描述很多,不过前些日子遇到了一个需要cookie才能登陆的网站 ...
- Android Activity学习笔记(一)
Activity为Android应用的四大组件之一,提供界面来与用户完成交互等操作.其中Activity的生命周期的知识这里做个笔记. Activity的生命周期由以下几个部分组成: 1.onCrea ...
- MySQL 插入与自增主键值相等的字段 与 高并发下保证数据准确的实验
场景描述: 表t2 中 有 自增主键 id 和 字段v 当插入记录的时候 要求 v与id 的值相等(按理来说这样的字段是需要拆表的,但是业务场景是 只有某些行相等 ) 在网上搜的一种办法是 先获取 ...
- EasyUI DataGrid 窗口大小自适用--------------未测试
EasyUI 新版本里添加了 fit 属性,不需要老版本的那么复杂,重新load DataGrid.但是昨天用的时间发现只有一个DataGrid的时候用fit:true 很好使,但是如果有其它元素,如 ...
- 借助LVS+Keepalived实现负载均衡(转)
原文:http://www.cnblogs.com/edisonchou/p/4281978.html 一.负载均衡:必不可少的基础手段 1.1 找更多的牛来拉车吧 当前大多数的互联网系统都使用了服务 ...
- Clean Code第三章<函数>
1.方法不要写太长,如果太长,抽取其中的逻辑到新的方法中 bad good 2.函数只做一件事 如果做了多件事,要在方法名里体现出来 3.每个函数一个抽象层级 4.函数名可以长一些,比长注释好 5.方 ...
- ZOJ 3195 Design the city LCA转RMQ
题意:给定n个点,下面n-1行 u , v ,dis 表示一条无向边和边权值,这里给了一颗无向树 下面m表示m个询问,问 u v n 三点最短距离 典型的LCA转RMQ #include<std ...
- HDU1896Stones(优先队列)
地址http://acm.hdu.edu.cn/showproblem.php?pid=1896 题目大一比较简单,就是说在一条直线道路上有n个石头,往前走,遇到一个数一个,如果遇到的是第奇数个那就把 ...
- Linux下Hadoop集群环境的安装配置
1)安装Ubuntu或其他Linux系统: a)为减少错误,集群中的主机最好安装同一版本的Linux系统,我的是Ubuntu12.04. b)每个主机的登陆用户名也最好都一样,比如都是hadoop,不 ...