本文主要内容:

在任务一中,用 四 种方式实现:点击界面按钮,开线程运行一段程序,结果显示在一个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://blog.csdn.net/lg1259156776/article/details/52332361

注:与此文相类似的文章已在前期有提到,此处只是重复强调而已!

【Qt开发】QThread 实用技巧、误区----但文档中没有提到的更多相关文章

  1. QThread 实用技巧、误区----但文档中没有提到

    本文主要内容: 在任务一中,用 四 种方式实现:点击界面按钮,开线程运行一段程序,结果显示在一个Label上.1. 用不正确的方式得到看似正确的结果2. 用Qt Manual 和 例子中使用的方法3. ...

  2. LaTeX技巧22:LaTeX文档中的参考文献初级

    用 LaTeX 处理文档, 经常就要书写参考文献, 本篇就是介绍如何在 LaTeX 中使用参考文献, 注意这里讲的是LaTeX默认的 thebibliography 环境, 如果要了解 LaTeX 中 ...

  3. AppleWatch开发教程之调试程序使用帮助文档

    AppleWatch开发教程之调试程序使用帮助文档 AppleWatch开发教程之调试程序 调试又被称为排错,是发现和减少程序错误的一个过程.在Xcode中进行调试的需要实现以下几个步骤: 1.添加断 ...

  4. 【VS开发】单文档中往视图中加入控件

    [VS开发]单文档中往视图中加入控件 标签(空格分隔): [VS开发] 分隔视图的但文档窗口,要显示控件,推荐使用CFormView或者CCtrlView,前者和对话框的做法一致. 在MainFram ...

  5. iTOP-4412开发板-can测试工具使用文档

    本文档介绍如何使用 can 工具测试 can. 给用户提供了“can_libs.rar”以及“can_tools.zip”压缩包,分别是 can 工具需要的库 文件和 can 工具二进制文件. 注意开 ...

  6. [.NET开发] C# 如何在PDF文档中创建表格

    表格能够直观的传达数据信息,使信息显得条理化,便于阅读同时也利于管理.那在PDF类型的文档中如何来添加表格并且对表格进行格式化操作呢?使用常规方法直接在PDF中添加表格行不通,那我们可以在借助第三方组 ...

  7. javascrit2.0完全参考手册(第二版) 第1章第1节 在XHTML文档中增加javascript

    通常,向文档中增加script脚本使用<script>元素,在HTML中增加脚本的方式有4中: (1)放到<script></script>块中: (2)<s ...

  8. 如何使用免费PDF控件从PDF文档中提取文本和图片

             如何使用免费PDF控件从PDF文档中提取文本和图片 概要 现在手头的项目有一个需求是从PDF文档中提取文本和图片,我以前也使用过像iTextSharp, PDFBox 这些免费的PD ...

  9. HTML结构文档中那些基础又重要又容易被忽略的事?

    HTML结构文档中那些基础又重要又容易被忽略的事? 大部分的人,总是会做出这样下意识地判断:简单就是不重要,容易就可以直接忽略掉!其实不然,简有精髓,基石必重,岂能略而不顾!HTML结构文档的编写,可 ...

随机推荐

  1. nodepad++ 正则 替换

    (?<k1>.*?)\=(?<k2>.*?); cookie.Add(new CookieList() { Key = "\1", Value = &quo ...

  2. vue-cli 本地数据模拟

    方法一: 使用express搭建静态服务 mock数据写在json文件中,proxyTable 里将接口代理到具体mock数据json文件上.具体方法: 创建 mock 文件夹 build/dev-s ...

  3. mySql索引优化分析

    MySQL索引优化分析 为什么你写的sql查询慢?为什么你建的索引常失效?通过本章内容,你将学会MySQL性能下降的原因,索引的简介,索引创建的原则,explain命令的使用,以及explain输出字 ...

  4. 字符串匹配算法KMP算法

    数据结构中讲到关于字符串匹配算法时,提到朴素匹配算法,和KMP匹配算法. 朴素匹配算法就是简单的一个一个匹配字符,如果遇到不匹配字符那么就在源字符串中迭代下一个位置一个一个的匹配,这样计算起来会有很多 ...

  5. RightScale 2019年云状态报告:公共云快速增长 微软Azure增长最快

    https://www.rightscale.com/ 全球云管理服务厂商RightScale发布了年度云状态报告,今年报告的十大主要内容包括:企业在多云平台上投入巨资.公共云继续快速增长,但是私有云 ...

  6. 支付宝对账单下载Java正式商户调用

    package code; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; impo ...

  7. iOS进阶指南试读之UI篇

    iOS进阶指南试读之UI篇 UI篇 UI是一个iOS开发工程师的基本功.怎么说?UI本质上就是你调用苹果提供给你的API来完成设计师的设计.所以,想提升UI的功力也很简单,没事就看看UIKit里的各个 ...

  8. grpc-golang实现账号and密码认证

    // I would recommend to use interceptors: // client grpc.Dial(target, grpc.WithPerRPCCredentials(&am ...

  9. Linux访问Windows共享目录的方法——smbclient

    之前写过Ubuntu访问Windows共享目录,里面主要介绍如何在GUI上访问windows共享目录. 本文侧重于整理通过命令行访问的方法. 先确保smbclient.ubuntu下可以直接运行下面命 ...

  10. nginx源码分析:打开监听套接字的流程

    问题源于在分析nginx的源码时,找了半天没有找到nginx是怎么把监听套接字读事件添加到事件循环中的,后经过仔细的分析,终于搞明白,于是记录一下. 在上一篇module机制中介绍了nginx添加mo ...