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也可以开启事件循环.只不 ...
随机推荐
- 【前端必会】tapable、hook,webpack的灵魂
背景 什么是tapable.hook,平时做vue开发时的webpack 配置一直都没弄懂,你也有这种情况吗? 还是看源码,闲来无聊又看一下webpack的源码,看看能否找到一些宝藏 tapable和 ...
- 一个终端工具竟然有AI功能?使用了1天我立马把其他终端全卸载了!太香了!
前言 平常工作需要频繁使用终端工具,有一个好的命令行终端工具是非常重要的. 尤其是使用mac的小伙伴,估计不少人都觉得iterm2才是最好的终端工具. 其实起初我也是这么觉得的,但是最近直到我使用了这 ...
- HDU1114 Piggy-Bank (完全背包)
完全背包模板,和01背包相比不用倒推,因为一种可以选多个. 这道题求最小,dp数组初始化为无穷即可. 1 #include<iostream> 2 #include<cstring& ...
- 使用EF Core更新与修改生产数据库
使用EF Core的Code First,在设计阶段,直接使用Database.EnsureCreated()和EnsureDeleted()可以快速删除.更新最新的数据结构.由于没有什么数据,删除的 ...
- struts2 标签总结
<s:if>判断字符串的问题: 1.判断单个字符:<s:if test="#session.user.username=='c'"> 这样是从session ...
- java的分页原理详解
首先先创建一个Student实体类. import java.io.Serializable; import java.util.Map; public class Student implement ...
- Java8新特性之Stream流(含具体案例)
一.概述 Stream 流是 Java 8 新提供给开发者的一组操作集合的 API,将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选.排序.聚合等.元素 ...
- C++ Undefined Behavior 详细列表
Undefined Behavior,即未定义的行为,指程序不可预测的执行效果,一般由错误的代码实现引起.出于效率.兼容性等多方面原因,语言标准不便于定义错误程序的明确行为,而是将其统称为" ...
- 抛砖系列之redis监控命令
前言 redis是一款非常流行的kv数据库,以高性能著称,其高吞吐.低延迟等特性让广大开发者趋之若鹜,每每看到别人发出的redis故障报告都让我产生一种居安思危,以史为鉴的危机感,恰逢今年十一西安烟雨 ...
- LcdTools如何通过PX01把EDP屏的EDID拷贝出来
PX01点EDP屏在上电过程会自动读取屏EDID,怎么把EDP EDID值拷贝出来呢? 在上电时序函数调用SetEdidRdShowEn(ON)指令开启EDID值读取显示功能.如下图 通过上述操作开机 ...