Qt在跨线程开发的时候可能会出现不少问题,在这里记录一下

Qt目前用下来还是非常强大的,虽然只是用在桌面端程序开发上,但是其强大的桌面开发库真的挺好用的(Layout除外,你妈死了)。

Qt除了UI,还有一些封装好的IO库,比如QFile和QTcpSocket等等,总的来说还是可以的。但问题是Qt总的来说还是一个封闭的框架,和非框架部分的兼容并没有想象中那么理想,就从之前的CLR库的转换就能看得出来了--但是你现实总是这样,你只能要求要么要么,不能既要又要,对吗?

扯远了,继续来说一下跨线程的问题,当然了这里的跨线程不是指在Qt生态内的跨线程,而是指和第三方一起操作的时候出现的跨线程问题。

首先不要以为跨线程离自己很远,跨线程无时无刻不在出现,比如你引用了第三方的DLL,你调的每一个接口,你都不知道里面会发生什么,尤其,尤其是回调函数,回调函数几乎不可避免的都是跨线程函数(你可以想象一下一个不跨线程的、阻塞的回调函数接口,提供给第三方,如果有这么个接口放给我我真的会想要杀了开发的的),然后就会出很多很傻逼的问题,我等会来举例。

典中典之比如跨线程调用QTcpSocket的 write接口,就会出现:

  1. socket notifiers cannot be enabled from another thread

要解决这个问题,直观的说就是不要跨线程操作,网上也有很多类似的说明。这也是有道理的,很多时候真的是设计问题导致的,因为设计失误出现了不应该有的跨线程操作。

当然也可以用信号和槽封装一下,但是这样会涉及很多不必要的代码,我个人觉得也太过于麻烦,就是给发送事件绑定一个信号,然后这个信号去触发一个操韩素华,这个槽函数内部才是发送tcpsocket通信的,这个方法是真的傻逼,我觉得如果是我写的这个类库,我肯定会发现这个傻逼问题,然后想个别的接口来解决这个问题。

那么我这里就提供一个更简单的方法,对QTcpSocket跨线程调用代码如下:

  1. QMetaObject::invokeMethod( &socket, std::bind( static_cast< qint64(QTcpSocket::*)(const QByteArray &) >( &QTcpSocket::write ), &socket, QByteArray( "xxxx" ) ) );

来分析一下这个 invokeMethod 调用,接口的定义是这样的

  1. 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部分:

  1. 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对象跨线程出现的问题记录,以及解决方案的更多相关文章

  1. 【QT】跨线程的信号槽(connect函数)

    线程的信号槽机制需要开启线程的事件循环机制,即调用QThread::exec()函数开启线程的事件循环. Qt信号-槽连接函数原型如下: bool QObject::connect ( const Q ...

  2. MFC、Qt、C#跨线程调用对象

    MFC.Qt.C#都是面向对象的编程库 1.MFC不允许跨线程调用对象,即线程只能调用它本身分配了空间的对象 In a multi-threaded application written using ...

  3. JNI加载Native Library 以及 跨线程和Qt通信

    Part1 Java Native Interface-JNI-JAVA本地调用 JNI标准是Java平台的一部分, 允许Java代码和其他语言进行交互; 开始实现-> Step 1) 编写Ja ...

  4. Qt跨线程调用错误解析及解决办法

    错误提示:Error: Cannot create children for a parent that is in a different thread. 错误案例分析 新建SerialLink子线 ...

  5. 关于Qt跨线程调用IO子类的理解

    一.疑问 突然想到,类似于QTcpsocket和QSerialport这类对象,如果是在A线程中new的,那就不能在其他线程中访问.我一般是这样做的: 封装一个QObject子类,放这些对象进去,然后 ...

  6. windows核心编程---第八章 使用内核对象进行线程同步

    使用内核对象进行线程同步. 前面我们介绍了用户模式下线程同步的几种方式.在用户模式下进行线程同步的最大好处就是速度非常快.因此当需要使用线程同步时用户模式下的线程同步是首选. 但是用户模式下的线程同步 ...

  7. PyQt5中的信号与槽,js 与 Qt 对象之间互相调用

    一.PyQt中的信号与槽 信号(Signal)和槽(Slot)是Qt中的核心机制,用在对象之间互相通信.在Qt中每个QObject对象和PyQt中所有继承自QWidget的控件(这些都是QObject ...

  8. C# Winform 跨线程更新UI控件常用方法汇总(多线程访问UI控件)

    概述 C#Winform编程中,跨线程直接更新UI控件的做法是不正确的,会时常出现“线程间操作无效: 从不是创建控件的线程访问它”的异常.处理跨线程更新Winform UI控件常用的方法有4种:1. ...

  9. 实现Winform 跨线程安全访问UI控件

    在多线程操作WinForm窗体上的控件时,出现“线程间操作无效:从不是创建控件XXXX的线程访问它”,那是因为默认情况下,在Windows应用程序中,.NET Framework不允许在一个线程中直接 ...

  10. QT中的线程与事件循环理解(2)

    1. Qt多线程与Qobject的关系 每一个 Qt 应用程序至少有一个事件循环,就是调用了QCoreApplication::exec()的那个事件循环.不过,QThread也可以开启事件循环.只不 ...

随机推荐

  1. influxDB2.2

    下载安装 下载地址 下载后在解压目录中,输入cmd执行exe文件 浏览器访问localhost:8086 选择快速开始,填写用户信息,组织信息 相关概念 InfluxDB是一个由InfluxData开 ...

  2. Jmeter——BeanShell 内置变量vars、props、prev的使用

    在使用Jmeter过程中,或多或少都会接触些BeanShell,它会使工具的使用,变得更灵活. Jmeter中关于BeanShell的有: 1.BeanShell Sampler 取样器:完成Bean ...

  3. React魔法堂:echarts-for-react源码略读

    前言 在当前工业4.0和智能制造的产业升级浪潮当中,智慧大屏无疑是展示企业IT成果的最有效方式之一.然而其背后怎么能缺少ECharts的身影呢?对于React应用而言,直接使用ECharts并不是最高 ...

  4. 2022-08-21-Freewind主题_cdn替换版

    layout: post cid: 16 title: Freewind主题 cdn替换版 slug: 16 date: 2022/08/21 14:06:00 updated: 2022/08/21 ...

  5. 【MySQL】Navicat15 安装

    # Navicat安装` 提示`:鉴于之间已经出了MySQL的安装教程,在这了我也讲下,那个其实包含了两个知识点,既可以小白初次安装MySQL客户端,也面向想安装5.x和8.x两个版本的. --- @ ...

  6. Windows下自动云备份思源笔记到Gitee

    前言 思源笔记是一款本地笔记为主的软件,其目前提供了148元/year的付费同步功能,但对于21世纪中国难民而言还是太贵啦. 条件允许的同学还是使用官方的同步,支持下作者. 所以,就在思考有没有白嫖的 ...

  7. 部署redis-cluster

    1.环境准备 ☆ 每个Redis 节点采用相同的相同的Redis版本.相同的密码.硬件配置 ☆ 所有Redis服务器必须没有任何数据 #所有主从节点执行: [root@ubuntu2004 ~]#ba ...

  8. Aspose.Cell篇章3,设置写入到Excel文件的各种样式及输出

    Aspose.Cell的Style.Number设置全部设置 /// <summary> /// 单元格样式编号 /// 0 General General /// 1 Decimal 0 ...

  9. 洛谷P4135 Ynoi2016 掉进兔子洞 (带权bitset?/bitset优化莫队 模板) 题解

    题面. 看到这道题,我第一反应就是莫队. 我甚至也猜出了把所有询问的三个区间压到一起处理然后分别计算对应询问答案. 但是,这么复杂的贡献用什么东西存?难道要开一个数组 query_appear_tim ...

  10. Re:从零开始教你使用 Sublime Text

    目录 Re:从零开始教你使用 Sublime Text 0.前言 0-0.关于我为什么要写这篇文章 0-1.关于这篇文章会讲什么 0-2.适用人群 0-4.其他 0-5.无用的统计 1.安装和基础功能 ...