Qt对象跨线程出现的问题记录,以及解决方案
Qt在跨线程开发的时候可能会出现不少问题,在这里记录一下
Qt目前用下来还是非常强大的,虽然只是用在桌面端程序开发上,但是其强大的桌面开发库真的挺好用的(Layout除外,你妈死了)。
Qt除了UI,还有一些封装好的IO库,比如QFile和QTcpSocket等等,总的来说还是可以的。但问题是Qt总的来说还是一个封闭的框架,和非框架部分的兼容并没有想象中那么理想,就从之前的CLR库的转换就能看得出来了--但是你现实总是这样,你只能要求要么要么,不能既要又要,对吗?
扯远了,继续来说一下跨线程的问题,当然了这里的跨线程不是指在Qt生态内的跨线程,而是指和第三方一起操作的时候出现的跨线程问题。
首先不要以为跨线程离自己很远,跨线程无时无刻不在出现,比如你引用了第三方的DLL,你调的每一个接口,你都不知道里面会发生什么,尤其,尤其是回调函数,回调函数几乎不可避免的都是跨线程函数(你可以想象一下一个不跨线程的、阻塞的回调函数接口,提供给第三方,如果有这么个接口放给我我真的会想要杀了开发的的),然后就会出很多很傻逼的问题,我等会来举例。
典中典之比如跨线程调用QTcpSocket的 write接口,就会出现:
socket notifiers cannot be enabled from another thread
要解决这个问题,直观的说就是不要跨线程操作,网上也有很多类似的说明。这也是有道理的,很多时候真的是设计问题导致的,因为设计失误出现了不应该有的跨线程操作。
当然也可以用信号和槽封装一下,但是这样会涉及很多不必要的代码,我个人觉得也太过于麻烦,就是给发送事件绑定一个信号,然后这个信号去触发一个操韩素华,这个槽函数内部才是发送tcpsocket通信的,这个方法是真的傻逼,我觉得如果是我写的这个类库,我肯定会发现这个傻逼问题,然后想个别的接口来解决这个问题。
那么我这里就提供一个更简单的方法,对QTcpSocket跨线程调用代码如下:
QMetaObject::invokeMethod( &socket, std::bind( static_cast< qint64(QTcpSocket::*)(const QByteArray &) >( &QTcpSocket::write ), &socket, QByteArray( "xxxx" ) ) );
来分析一下这个 invokeMethod 调用,接口的定义是这样的
bool invokeMethod(QObject *context, Functor function, Qt::ConnectionType type = Qt::AutoConnection, FunctorReturnType *ret = nullptr)
context:表示被调用的函数要在 哪个对象 的生存线程运行
function:被调用的函数
主要看这两个,后面都有缺省值,不用管。
在本例中context指定socket,就表示在socket的生存线程运行,这可能是任何线程,取决于你在哪里实例化这个socket。如果填写qApp,就表示指定在主线程运行。
function被赋值了一个std:bind,这是因为write不是槽函数,使用起来还是有点麻烦,不能直接写名字走moc系统。所以要手动用std::bind把函数给包起来。
关于这个std::bind的3部分:
std::bind( static_cast< qint64(QTcpSocket::*)(const QByteArray &) >( &QTcpSocket::write ), &socket, QByteArray( "xxxx" ) )
static_cast< qint64(QTcpSocket::)(const QByteArray &) >( &QTcpSocket::write ):QTcpSocket中,叫write的有很多个,所以要依靠 qint64(QTcpSocket::)(const QByteArray &) 去指定出来是哪一个。这是C++的方法,和Qt无关。
&socket:表示要执行谁的write,有点类似于指针的角色
QByteArray( "xxxx" ):调用write时给的参数
除了IO相关的类,其他有一些Qt的类也不可以跨线程操作,比如说QTimer,也会报错
QObject::startTimer: Timers cannot be started from another thread
按照上面说的调用原理,可以这样写:
QMetaObject::invokeMethod( &timer, std::bind( static_cast< void(QTimer:)(int) >( &QTimer::start ), &timer, 1000 ) );
对了,start是一个槽函数,所以如果借助moc系统的话,可以这样写(两个写法是等价的)
QMetaObject::invokeMethod( &timer, "start", Q_ARG( int, 1000 ) );
注意!在QMetaObject::invokeMethod配合std::bind使用的时候,5.10.0版本的Qt会有内存泄漏,bug如下:
https://bugreports.qt.io/browse/QTBUG-65462
请注意你的Qt版本,以及bug的修复情况,酌情使用这个方法。
两个实际问题,可以参考一下,以防止后续的开发出现类似的问题:
1.回调函数调用一个Qt弹窗DLL出错
之前有一个弹窗,就是当我们接收到回调函数的信号,就向外弹出一个窗口,然后这个窗口是个Dialog,会返回一个int值,根据这个int值来决定结果,挺常见的,对吧?
但是这个弹窗有个问题,就是第一次弹出来之后有可能会导致整个程序崩溃,但是为什么会这样?我和戴工检查了一轮下来,发现是DLL内部的 paintEvent崩溃导致的,那么有两个问题,一为什么paintEvent崩溃会导致这个画面,二是为什么paintEvent会崩溃?
其实这个paintEvent崩溃是有弹出提示的,我不记得具体是什么了,反正好像是this IO Device can only paint once,大致意思就是这个绘制器一次就只能绘制一个画面。
绘制器是没问题的,我以为是同线程下的提示框和当前广播框之间抢占线程了导致的问题,于是我把提示框放在了整个程序绘制完成之后,但是并没有用,该崩溃仍然崩溃。
我在想是不是因为调用了两次导致的,于是我进行了测试
一、写Demo同时打开两次这个提示框是没问题的
二、修改了一次只打开一个提示框,仍然报错。
其实显然不是这个问题,因为提示框和主线程是保持一致的,所以无论如何都会保持顺序绘制,不会出现绘制器抢占的问题。
最后排查一路,最后发现问题还是出现在IO Device对跨线程调用上。因为第一个启动的窗口是通过回调函数启动的(其实其他函数也是通过回调函数启动的,但是非常巧合的是我在设计时后续启动的窗口时通过信号槽机制来启动的,所以即使是跨线程依然运行正常)
首先第一点,Qt IO Device本身在跨线程调用的时候就有可能出现抢占问题,不论当前是否在绘制(更何况其实就是在绘制,我的LiveEffect一直在不停地绘制...)于是跨线程调用绘制器就出现了抢占,搞得整个程序的IODevice崩溃了,这也是为什么我拔插摄像头之后画面又可以了,因为这个画面我让它强制刷新了...也就是说绘制器又被重新生成了一遍...这也是让我非常难理解问题的一个点。
怎么解决?
法一、其实非常简单,就是在回调函数那里把直接调用函数改为发送一个信号,让 类内的函数 改为槽函数去接收就可以了。
法二、调用上述介绍的方法,也可以的,跨线程的操作必须调用其内部的跨线程方法,不然可能会出现很多意想不到的错误和崩溃
Qt对象跨线程出现的问题记录,以及解决方案的更多相关文章
- 【QT】跨线程的信号槽(connect函数)
线程的信号槽机制需要开启线程的事件循环机制,即调用QThread::exec()函数开启线程的事件循环. Qt信号-槽连接函数原型如下: bool QObject::connect ( const Q ...
- MFC、Qt、C#跨线程调用对象
MFC.Qt.C#都是面向对象的编程库 1.MFC不允许跨线程调用对象,即线程只能调用它本身分配了空间的对象 In a multi-threaded application written using ...
- JNI加载Native Library 以及 跨线程和Qt通信
Part1 Java Native Interface-JNI-JAVA本地调用 JNI标准是Java平台的一部分, 允许Java代码和其他语言进行交互; 开始实现-> Step 1) 编写Ja ...
- Qt跨线程调用错误解析及解决办法
错误提示:Error: Cannot create children for a parent that is in a different thread. 错误案例分析 新建SerialLink子线 ...
- 关于Qt跨线程调用IO子类的理解
一.疑问 突然想到,类似于QTcpsocket和QSerialport这类对象,如果是在A线程中new的,那就不能在其他线程中访问.我一般是这样做的: 封装一个QObject子类,放这些对象进去,然后 ...
- windows核心编程---第八章 使用内核对象进行线程同步
使用内核对象进行线程同步. 前面我们介绍了用户模式下线程同步的几种方式.在用户模式下进行线程同步的最大好处就是速度非常快.因此当需要使用线程同步时用户模式下的线程同步是首选. 但是用户模式下的线程同步 ...
- PyQt5中的信号与槽,js 与 Qt 对象之间互相调用
一.PyQt中的信号与槽 信号(Signal)和槽(Slot)是Qt中的核心机制,用在对象之间互相通信.在Qt中每个QObject对象和PyQt中所有继承自QWidget的控件(这些都是QObject ...
- C# Winform 跨线程更新UI控件常用方法汇总(多线程访问UI控件)
概述 C#Winform编程中,跨线程直接更新UI控件的做法是不正确的,会时常出现“线程间操作无效: 从不是创建控件的线程访问它”的异常.处理跨线程更新Winform UI控件常用的方法有4种:1. ...
- 实现Winform 跨线程安全访问UI控件
在多线程操作WinForm窗体上的控件时,出现“线程间操作无效:从不是创建控件XXXX的线程访问它”,那是因为默认情况下,在Windows应用程序中,.NET Framework不允许在一个线程中直接 ...
- QT中的线程与事件循环理解(2)
1. Qt多线程与Qobject的关系 每一个 Qt 应用程序至少有一个事件循环,就是调用了QCoreApplication::exec()的那个事件循环.不过,QThread也可以开启事件循环.只不 ...
随机推荐
- 监控elasticsearch
转载自:https://cloud.tencent.com/developer/article/1655489 注意:上半截跟下半截是采用的不同的方式,建议采用下半截的方式,上半截的方式据说获取不到数 ...
- linux搭建内网邮件服务器
一.配置发件服务器 1.1 根据现场IP,配置主机名 vi /etc/hosts 192.168.40.133 mail.test.com 将主机名更改为邮件服务器域名mail.test.com 1. ...
- 工厂想采购一套信息化生产执行系统mes,不知道用哪家比较好?
好的信息化生产执行系统MES多的是,但是否适用于贵工厂那就不得而知了,要知道,不同行业.不同产品.不同规模的工厂用同一套系统效果呈现出来都不一样的,所以匹配很重要,个性化差异化.变化性等决定了一个工厂 ...
- C#-1 .Net框架
一 .Net框架组成分为三部分:公共语言运行时CLR.框架类库FCL和编程工具 1.CLR:公共语言运行时(Common Language Runtime): 是一个运行时环境负责代码安全验证.代码执 ...
- OpenDataV低代码平台增加自定义属性编辑
上一篇我们讲到了怎么在OpenDataV中添加自己的组件,为了让大家更快的上手我们的平台,这一次针对自定义属性编辑,我们再来加一篇说明.我们先来看一下OpenDataV中的属性编辑功能. 当我们拖动一 ...
- hive数据导出到linux本地
方法1(hive下执行):insert overwrite local directory 'Linux本地目录' row format delimited fields terminated by ...
- sg函数入门理解
首先理解sg函数必须先理解mex函数 mex是求除它集合内的最小大于等于0的整数,例:mex{1,2}=0:mex{2}=0:mex{0,1,2}=3:mex{0,5}=1. 而sg函数是啥呢? 对于 ...
- 从零开始学Graph Database:什么是图
摘要:本文从零开始引导与大家一起学习图知识.希望大家可以通过本教程学习如何使用图数据库与图计算引擎.本篇将以华为云图引擎服务来辅助大家学习如何使用图数据库与图计算引擎. 本文分享自华为云社区<从 ...
- HTML+CSS基础知识(5)相对定位、绝对定位、固定定位
文章目录 1.相对定位 1.1 代码 1.2 测试结果 2.绝对定位 2.1 代码 2.2 测试 3.固定定位 3.1 代码 3.2 测试结果 1.相对定位 1.1 代码 <!DOCTYPE h ...
- Unity坐标系入门
一.坐标系的概念 Unity 世界坐标系采用左手坐标系,大拇指指向X轴(红色),食指指向Y轴(黄色),中指向手心方向歪曲90度表示Z轴(蓝色),同时Z轴也是物体前进方向,下图表示Unity的四种坐标系 ...