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也可以开启事件循环.只不 ...
随机推荐
- 20220929-ArrayList扩容机制源码分析
示例代码 public class ArrayListSource { public static void main(String[] args) { ArrayList arrayList = n ...
- nsis利用ButtonEvent插件移动无标题窗口
众所周知,普通win窗口是带有标题栏的,标题栏的主要功用之一,就是可以方便的拖动窗体,但为了各式各样的目的,有时候我们不得不想办法将其消除,在nsis中主要是靠system插件调用系统函数改变窗体风格 ...
- SECS半导体设备通讯-3 SECS-II通信标准
一 SECS-II 概述 SECS-II 标准定义了使用如SECS-I.HSMS等传输协议在设备和主机之间交换的消息的形式和含义. 定义了以消息的形式在设备和主机之间传递信息,消息按其行为分类,称为S ...
- vulnhub靶场|NAPPING: 1.0.1
准备: 攻击机:虚拟机kali.本机win10. 靶机:NAPPING: 1.0.1,地址我这里设置的桥接,,下载地址:https://download.vulnhub.com/napping/nap ...
- if、where、trim、choose、when、otherwise、foreach
1.if if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行:反之标签中 的内容不会执行 <!--List<Emp> getEmpListBy ...
- Codeforces1695 D1.+D2 Tree Queries
题意 给一个n个点的无向图,其中有一个隐藏点X,可以进行一组询问S来确定S是n个节点中的哪个点.S包括k个询问节点.询问返回的值也为k个值,每个值为X点到每个询问节点的最短路距离,求k最小为多少. 提 ...
- python基础爬虫,翻译爬虫,小说爬虫
基础爬虫: # -*- coding: utf-8 -*- import requests url = 'https://www.baidu.com' # 注释1 headers = { # 注释2 ...
- 一、Django介绍
一.Django介绍 Python下有许多款不同的 Web 框架.Django是重量级选手中最有代表性的一位.许多成功的网站和APP都基于Django.Django 是一个开放源代码的 Web 应用框 ...
- 转载:Python 实现百度翻译
来源: https://blog.csdn.net/qq_44814439/article/details/105642066 作者: Chloemxc 功能: Python 实现百度翻译 from ...
- RDF/RDFS/OWL
RDF(Resource Description Framework 资源描述框架) 知识总是以三元组形式出现: (subject, predicate, object) 即 (主,谓,宾) 资源和属 ...