QThread 实用技巧、误区----但文档中没有提到
本文主要内容:
在任务一中,用 四 种方式实现:点击界面按钮,开线程运行一段程序,结果显示在一个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在次线程中
原文链接:http://hi.baidu.com/cyclone/blog/item/65f3f603294f2e783812bb51.html
QThread 实用技巧、误区----但文档中没有提到的更多相关文章
- 【Qt开发】QThread 实用技巧、误区----但文档中没有提到
本文主要内容: 在任务一中,用 四 种方式实现:点击界面按钮,开线程运行一段程序,结果显示在一个Label上.1. 用不正确的方式得到看似正确的结果2. 用Qt Manual 和 例子中使用的方法3. ...
- LaTeX技巧22:LaTeX文档中的参考文献初级
用 LaTeX 处理文档, 经常就要书写参考文献, 本篇就是介绍如何在 LaTeX 中使用参考文献, 注意这里讲的是LaTeX默认的 thebibliography 环境, 如果要了解 LaTeX 中 ...
- javascrit2.0完全参考手册(第二版) 第1章第1节 在XHTML文档中增加javascript
通常,向文档中增加script脚本使用<script>元素,在HTML中增加脚本的方式有4中: (1)放到<script></script>块中: (2)<s ...
- 01将图片嵌入到Markdown文档中
将图片内嵌入Markdown文档中 将图片嵌入Markdown文档中一直是一个比较麻烦的事情.通常的做法是将图片存入本地某个路径或者网络存储空间,使用URL链接的形式插入图片: ![image][ur ...
- C# 在Word文档中生成条形码
C# 在Word文档中生成条形码 简介 条形码是由多个不同的空白和黑条按照一定的顺序组成,用于表示各种信息如产品名称.制造商.类别.价格等.目前,条形码在我们的日常生活中有着很广泛的应用,不管是在图书 ...
- C# 提取Word文档中的图片
C# 提取Word文档中的图片 图片和文字是word文档中两种最常见的对象,在微软word中,如果我们想要提取出一个文档内的图片,只需要右击图片选择另存为然后命名保存就可以了,今天这篇文章主要是实现使 ...
- 如何使用免费PDF控件从PDF文档中提取文本和图片
如何使用免费PDF控件从PDF文档中提取文本和图片 概要 现在手头的项目有一个需求是从PDF文档中提取文本和图片,我以前也使用过像iTextSharp, PDFBox 这些免费的PD ...
- java使用正则从爬虫爬的txt文档中提取QQ邮箱
我的需求是从一堆文档中提取出qq邮箱,写了这篇帖子,希望能帮助和我有一样需求的人,谢谢!...... import java.io.BufferedReader; import java.io.Fil ...
- [No00005B] word快速插入当前时间&怎样一次性删除文档中的全部链接
按Alt+Shift+D键来插入系统日期 按Alt+Shift+T键则插入系统当前时间 同时,在插入的时间上右键->编辑域 一次性删除文档中的全部链接: 方法1:一劳永逸法(推荐) 因为链接大多 ...
随机推荐
- 【Nodejs】理想论坛帖子下载爬虫1.04
一直想做一个能把理想论坛指定页范围的帖子都能完整下载下来的爬虫,但未能如愿. 主要的障碍在并发数的控制和长时间任务的突然退出,比如想下载前五页的帖子,分析后可得到大约15000个主贴或子贴,如果用回调 ...
- Missing styles. Is the correct theme chosen for this layout? Use the Theme combo box above the layou
android无法静态显示ui效果. Missing styles. Is the correct theme chosen for this layout? Use the Theme combo ...
- Step by Step iOS Project In Action - 视图控制器
1. 什么是视图控制器(View Controller) 简单来说,视图控制器用来管理你所有的视图. 他们是你的视图和模型的粘合剂. 如果你做过MVC的Web项目,我想你应该不会对它感到陌生. 2. ...
- ZH奶酪:【数据结构与算法】搜索之BFS
1.目标 通过本文,希望可以达到以下目标,当遇到任意问题时,可以: 1.很快建立状态空间: 2.提出一个合理算法: 3.简单估计时空性能: 2.搜索分类 2.1.盲目搜索 按照预定的控制策略进行搜索, ...
- T-SQL 之 游标
游标是面向行的,它会使开发人员变懒,懒得去想用面向集合的查询方式实现某些功能. 在性能上,游标会占有更多的内存,减少可用的并发,占用带宽,锁定资源,当然还有更多的代码量. 用一个比喻来说明为什么游标会 ...
- Inside GDALAllRegister之四: 跳过driver
这个函数很短小: /** * \brief This method unload undesirable drivers. * * All drivers specified in the space ...
- Tomcat环境的搭建(web基础学习笔记一)
一.下载和安装Tomcat服务器 下载Tomcat安装程序包:http://tomcat.apache.org/ 点击[Download]跳转到如下图所示的下载页面 二.点击左侧要下载的版本,选择To ...
- vb中adOpenKeyset, adLockOptimistic
adOpenStatic 向前游标adOpenKeyset 键集游标adLockOptimistic设置窗口为固定的大小 附带一个小资料: ------------------------------ ...
- Circular dependency issuse on cocoapods version(0.36.0) 全然解决方式(非降版本号)
此前的文章中.以前提到在cocoapods依赖管理版本号假设超过0.34.4.比方0.35, 0.36.0等版本号中. 运行"pod install" 或者 "pod u ...
- 〖Android〗OK6410a的Android HAL层代码编写笔记
一.编写LED灯的Linux驱动程序代码 之所以使用存在HAL层,是为了保护对硬件驱动过程的逻辑与原理: 所以,残留在Linux驱动层的代码,只保留了基本的读写操作,而不含有关键的逻辑思维: 1. l ...