找回密码
 立即注册
收起左侧

浅谈QThread的基本用法和注意事项

7
回复
11132
查看
[复制链接]
累计签到:882 天
连续签到:3 天
来源: 原创 2016-6-26 22:59:03 显示全部楼层 |阅读模式

马上注册,查看详细内容!注册请先查看:注册须知

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
本帖最后由 一叶知秋 于 2016-7-2 21:36 编辑

前言
在你阅读这篇文件之前,如果你没有仔细阅读QThread类的官方帮助文档,我非常肯定的建议你去读一下(当然你也可以结合着我的这篇文章看)。当我们还是一个新手时,依葫芦画瓢是一个必经的过程(不排除你是天才),但是当你有了一定的工作经验,有了一定的理解力的时候,你就要尝试着去做一些以前做不到的事情,这对以后的职业道路发展是非常有帮助的。由于楼主能力有限,文章中有错误或不足的地方,欢迎大家拍砖。技术这种东西是越辩越明白,千万不能揣着糊涂当明白,不然早晚有一天是混不下去。

随着现代电子技术和互联网的告诉发展,现在的很多电子设备,如电脑、平板、手机等都已经进入了多核多线程时代。那么,作为软件技术开发人员,掌握多线程编程技术是非常必要的。今天我就结合自己的工作经历,开发文档和实验来谈一谈Qt中线程的
基本用法和注意事项

一、阐明释义
对于使用过Qt的技术人员来说,一谈到线程就必然想到QThread类,甚至一些新手就将QThread认作是线程
,其实这是不恰当的。然我们来看一下官方文档的描述,说
The QThread class provides a platform-independent way to manage threads.”,翻译过来就是说“QThread类提供了一个平台无关的方式去管理线程”。这是官方文档Detailed Description的第一句话,这就是对QThread类的一个明确定义。管理线程中的管理字眼很重要,它说明了一种间接性,也就是说我们是通过某种间接的手段达到了我们的目的,而不是直接的。举个例子,对于使用过数据库的技术人员来说,大多数人都会选择一个简单易用的界面友好的数据库管理工具,该种工具对提高开发人员工作效率来说是非常重要的。那么问题来了,你能够说这种数据库管理工具就是数据库吗,显然不能。QThread类就类似于这种数据库管理工具,它隐藏了实现细节,暴露给开发人员哪些必要的东西。我认为这就是Qt的一个优势所在,一者它降低了学习使用的门槛,二者它简化了使用的方式,这才符合现**发语言的大方向。这种优势带来的好处就是是开发人员能够快速的完成功能开发,缩短软件开发周期,时间就是金钱呐! --- 技术知识一种实现手段,而不应该成为一种被膜拜的对象(我是一个使用主义者。。。)

二、moveToThread的用法及注意事项
这种方法是Qt官方推荐的方法,使用方法就是定义一个类继承自QObject,然后将其moveToThread到一个QThread类对象中,然后通过信号和槽的方式来达到槽中的代码在线程中执行的目的,官方文档上有一段实例代码可供参考。官方文档描述中有一段话说明了为什么推荐大家使用这种方法,“
However, you are free to connect the Worker's slots to any signal, from any object, in any thread. It is safe to connect signals and slots across different threads, thanks to a mechanism called .”翻译过来就是“然而,你可以随意的在任何线程,从任何对象,使用任何信号去连接Worker(示例中要放在线程中的类对象)的槽,跨不同线程的信号和槽连接是安全的,感谢队列连接的机制”。这话说的很明白,这种方式就是你使用线程的优先选择,足以用来处理大多数情况。
结合我自己的实验情况,提出以下几条注意点:
1.GUI类对象(所有的窗口类)是不能在线程中创建的,他们必须在主线程中被创建。也就是说你不能使用如QLabel label 或 QLabel *label = new QLabel这用的代码。这种情况可以编译通过,但是运行的时候回挂掉。
2.注意在上一条中我使用的是不能在线程中“创建”而不是“使用”GUI类对象,这个很重要。经测试,将主线程中界面上的窗口部件的指针通过信号和槽的方式传到线程中使用是完全可以的。这个是很有意义的,比如你的界面上有一张表哥需要从数据库中查找并填充很多数据,这是一个很耗时的动作,可能会冻结你的界面。而如果通过指针的方式将其传递到数据库操作线程中去处理,将会很好的解决这个问题(当然也可以采用其他策略,如手机上的刷新加载方式)。关于这一点,我没有在官文中找到确切的字眼,欢迎讨论。
3.在线程起来之后,通过发送信号执行的槽才是有效的在线程中执行的
4.在主线程中直接通过指针的方式如 worker->dowork(),无论dowork()是普通的public函数还是public slots函数,都是在主线程中执行的,切记!
5.如果槽函数是在线程中执行的,那么在槽函数中调用的其他函数也是在线程中执行的

三、subclasssing QThread(子类化QThread)方式
大多数情况下是没有必要子类化QThread的,除非你想实现高级的线程管理功能。在Qt4.4之前,你只有通过这种方法使用线程,但是之后就不再推荐大家去这样使用了,所以这里就不在详细的介绍其使用方法了。
需要记住的是一个QThread对象通常情况下是存在于它被创建的线程(一般是主线程),而不是它所管理的线程,也就意味着,QThread的槽函数将在拥有它的线程中执行,而不是它所管理的线程。这就违反了使用者的本意,这一点很容易被忽视。正是基于这些原因,所以不推荐这样去用,因为很容易出错。

无论上述哪种使用方式,如果想终止线程,可以通过调用exit()或quit()来完成。在一些极端情况下,你可能想强制结束正在运行的线程,你可以通过调用terminate()函数来完成,但是这样是非常危险的,所以不推荐。

最后再介绍一下connect函数的第5个参数吧,通常情况下我们是不需要关注的,因为的默认的参数会根据情况来做出正确的选择。这里重点介绍一下前面三个类型,来辅助大家理解这部分内容。

首先介绍Qt:irectConnection,使用这个参数后,当信号发出的时候,对应的槽会被立即执行;
再来介绍Qt:ueuedConnection,这个参数对这篇文章将的内容很重要,由于有了这种方式,我们的moveToThread方式才可以正常工作。它的作用是当控制权回到接收者线程的事件循环时,该槽函数将在接收者线程中被执行。
最后再介绍一下Qt::AutoConnection,这是默认选项,如果发送信号的对象和接收者对象不再同一个线程,那么其效果等同于Qt:ueuedConnection,否则等同于Qt:irectionConnection,所以我们通常情况下不写这个参数也能正常工作,就是这个原因。

到此本文就结束了,欢迎拍砖!
回复

使用道具 举报

累计签到:882 天
连续签到:3 天
2016-6-26 23:19:35 显示全部楼层
本帖最后由 一叶知秋 于 2016-7-2 21:19 编辑

好长时间没写文章,特地打了草稿,就是字太丑。。。








本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复 支持 1 反对 0

使用道具 举报

累计签到:955 天
连续签到:1 天
2016-6-27 08:54:15 显示全部楼层
不错,原创好文。。。。
回复 支持 反对

使用道具 举报

累计签到:595 天
连续签到:1 天
2016-6-27 09:03:05 显示全部楼层
文章写的不错哦,就是字写的不认真
回复 支持 反对

使用道具 举报

尚未签到

2016-6-27 21:27:26 显示全部楼层
哈哈哈,不错不错,只是不懂为什么要手写草稿
回复 支持 反对

使用道具 举报

累计签到:7 天
连续签到:4 天
2020-7-22 10:39:17 显示全部楼层
本帖最后由 IceSea 于 2020-7-22 10:48 编辑

楼主您好,我想请问一下如何在前台播放一个加载动画(使用QMovie播放一个gif)的情况下,创建一个复杂的GUI对象(大概会需要数秒钟才能完成初始化操作)。如果在主线程中创建的话,创建刚开始执行,gif就不动了,界面就像卡死了一样,这样显然是不合适的。
我也尝试了在QThread中实例化,但正如您在文中提到的“GUI类对象(所有的窗口类)是不能在线程中创建的”,如果在QThread中实例化GUI类对象,使用emit把该对象传回主线程,show的话会挂。
那有什么方法既能实例化这个耗时的GUI对象,又能在创建过程中gif不会卡死呢?还请赐教。

回复 支持 反对

使用道具 举报

累计签到:39 天
连续签到:1 天
2020-7-24 17:50:02 显示全部楼层
IceSea 发表于 2020-7-22 10:39
楼主您好,我想请问一下如何在前台播放一个加载动画(使用QMovie播放一个gif)的情况下,创建一个复杂的GUI ...

你在创建GUI的时候有什么耗时的操作吗,耗时的操作可以放在子线程中去,操作完放回一个信号用来关闭加载动画
回复 支持 反对

使用道具 举报

累计签到:7 天
连续签到:4 天
2020-7-24 18:58:58 显示全部楼层
XHLin 发表于 2020-7-24 17:50
你在创建GUI的时候有什么耗时的操作吗,耗时的操作可以放在子线程中去,操作完放回一个信号用来关闭加载 ...

这个复杂的GUI对象也是由好几个组件构成的,所以初始化该对象的主要工作就是初始化它的几个构成组件,有的组件初始化是新建几个线程处理复杂操作,比如目标识别,这几个线程需要一直运行;有的组件初始化就是读取数据库,可视化到界面上。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了