<Python>PyQt5中在两个线程间传递数据实例记录

前言
在测试利用python调用AI模型API生成图像的程序时候,发现AI模型生成图像有一定的时间,此时UI界面会卡顿,就想到利用多线程来处理,然后就发现多线程之间的数据传递问题,在网络上搜索了相关资料后,一番折腾总算搞清楚了,于是本文作为一个记录,主要是为了以后遇到同样的问题,可以方便参考,当然,如果你有同样的需求,也可以参考本文。

实际需求:最近在使用智谱AI的图像大模型,集成到python中,使用PyQt5作为UI界面,然后就遇到了线程间数据传递的问题。

示例:在主界面添加两个按钮、一个文本框,两个按钮分别表示更新开始和更新复位,文本框实时更新进度值,进度值是利用循环来模拟。

不使用线程
先来看一下不使用子线程的示例:
代码:

import sys
import threading
import timefrom PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *class Test11(QMainWindow):def __init__(self):super().__init__()self.initUI()def initUI(self):self.btn0=QPushButton('更新开始',self)self.btn0.setGeometry(100,100,80,40)self.btn0.clicked.connect(self.countstart_func)self.btn1=QPushButton('更新复位',self)self.btn1.setGeometry(100,160,80,40)self.btn1.clicked.connect(self.countstop_func)self.te1=QTextEdit(self)self.te1.setGeometry(200,100,80,20)self.te1.setText('0')self.setWindowTitle('线程数据传递示例')self.setGeometry(100,100,400,400)self.show()def countstart_func(self):# thread1=threading.Thread(name='th1',target=self.counter_func)# thread1.start()self.counter_func()def counter_func(self):"""更新进度值"""for i in range(100):time.sleep(0.05)self.te1.setText(str(i))def countstop_func(self):"""重置进度值"""self.te1.setText('0')if __name__ == '__main__':app=QApplication(sys.argv)tt=Test11()sys.exit(app.exec_())

演示:
在这里插入图片描述
通过上面的演示可以发现,点击“更新开始”按钮后,界面就卡住了,然后等待一段时间后,文本框的数值变成99,这期间文本框无法实时更新数值。

那么,我们使用线程来看看:
直接使用线程

import sys
import threading
import timefrom PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *class Test11(QMainWindow):def __init__(self):super().__init__()self.initUI()def initUI(self):self.btn0=QPushButton('更新开始',self)self.btn0.setGeometry(100,100,80,40)self.btn0.clicked.connect(self.countstart_func)self.btn1=QPushButton('更新复位',self)self.btn1.setGeometry(100,160,80,40)self.btn1.clicked.connect(self.countstop_func)self.te1=QTextEdit(self)self.te1.setGeometry(200,100,80,20)self.te1.setText('0')self.setWindowTitle('线程数据传递示例')self.setGeometry(100,100,400,400)self.show()def countstart_func(self):thread1=threading.Thread(name='th1',target=self.counter_func)thread1.start()#self.counter_func()def counter_func(self):"""更新进度值"""for i in range(100):time.sleep(0.05)self.te1.setText(str(i))def countstop_func(self):"""重置进度值"""self.te1.setText('0')if __name__ == '__main__':app=QApplication(sys.argv)tt=Test11()sys.exit(app.exec_())

可以看到,上面的代码中,调用循环函数时,新建了一个线程,来运行一下看看:
在这里插入图片描述
可以看到,程序运行后,点击“更新开始”按钮,程序直接报错了:

QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTextDocument(0x27bf228b2f0), parent's thread is QThread(0x27bf027dd10), current thread is QThread(0x27bf2ad45e0)

报错的解释:

这个错误信息表明你试图在一个线程中创建一个子对象,而该子对象的父对象位于另一个线程中。在Qt中,对象的父对象和子对象必须在同一个线程中创建和管理,这是为了确保线程安全。

也就是说,直接在新建线程中更新文本框,属于是两个线程间的操作,是不支持的,所以需要采取其他方法。

利用信号、槽在线程间传递数据
既然不能直接在两个线程间操作数据,那就需要间接来对数据进行传递:

import sys
import threading
import timefrom PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *class countcount(QThread):data1=pyqtSignal(str)def run(self):self.counter_func()def counter_func(self):"""更新进度值"""for i in range(100):time.sleep(0.05)self.data1.emit(str(i))class Test11(QMainWindow):def __init__(self):super().__init__()self.initUI()def initUI(self):self.btn0=QPushButton('更新开始',self)self.btn0.setGeometry(100,100,80,40)self.btn0.clicked.connect(self.countstart_func)self.btn1=QPushButton('更新复位',self)self.btn1.setGeometry(100,160,80,40)self.btn1.clicked.connect(self.countstop_func)self.te1=QTextEdit(self)self.te1.setGeometry(200,100,80,20)self.te1.setText('0')self.setWindowTitle('线程数据传递示例')self.setGeometry(100,100,400,400)self.show()def countstart_func(self):self.thread1=countcount()self.thread1.data1.connect(self.func1)self.thread1.start()#self.counter_func()@pyqtSlot(str)def func1(self,data):self.te1.setText(data)def countstop_func(self):"""重置进度值"""self.te1.setText('0')if __name__ == '__main__':app=QApplication(sys.argv)tt=Test11()sys.exit(app.exec_())

可以看到,代码进行较大的改变,首先是新建了一个线程类:

class countcount(QThread):data1=pyqtSignal(str)def run(self):self.counter_func()def counter_func(self):"""更新进度值"""for i in range(100):time.sleep(0.05)self.data1.emit(str(i))

在这个countcount类中定义了一个信号data1,被定义为字符类型的数据,用于向外界传递线程内部的数据,此例中,传递的就是循环中间的每一个数值。
另外,要注意到,原本写在主线程程序中的循环函数,被放到了自定义的线程类中,就是counter_func这个函数。

然后在主线程的调用:

  def countstart_func(self):self.thread1=countcount()self.thread1.data1.connect(self.func1)self.thread1.start()#self.counter_func()@pyqtSlot(str)def func1(self,data):self.te1.setText(data)

在主线程中,将之前自定义的线程类countcount实例化为self.thread1,然后将自定义线程中定义的信号data1与主线程的函数self.func1连接起来。
即,每当data1这个信号触发时:

self.data1.emit(str(i))

都会调用self.func1函数,并且将data1的值传递给func1。

@pyqtSlot(str)def func1(self,data):self.te1.setText(data)

在本例中,data1就是循环中的实时值,然后每更新一次,就会调用func1,func1中会将data1的值传到文本框中。
这样就实现了子线程和主线程之间的数据传递。
演示:
在这里插入图片描述
完整代码:

import sys
import threading
import timefrom PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *class countcount(QThread):data1=pyqtSignal(str)def run(self):self.counter_func()def counter_func(self):"""更新进度值"""for i in range(100):time.sleep(0.05)self.data1.emit(str(i))class Test11(QMainWindow):def __init__(self):super().__init__()self.initUI()def initUI(self):self.btn0=QPushButton('更新开始',self)self.btn0.setGeometry(100,100,80,40)self.btn0.clicked.connect(self.countstart_func)self.btn1=QPushButton('更新复位',self)self.btn1.setGeometry(100,160,80,40)self.btn1.clicked.connect(self.countstop_func)self.te1=QTextEdit(self)self.te1.setGeometry(200,100,80,20)self.te1.setText('0')self.setWindowTitle('线程数据传递示例')self.setGeometry(100,100,400,400)self.show()def countstart_func(self):self.thread1=countcount()self.thread1.data1.connect(self.func1)self.thread1.start()#self.counter_func()@pyqtSlot(str)def func1(self,data):self.te1.setText(data)def countstop_func(self):"""重置进度值"""self.te1.setText('0')if __name__ == '__main__':app=QApplication(sys.argv)tt=Test11()sys.exit(app.exec_())

注:最后再说明一下,本文主要是为了作一个记录,方便以后遇到类似问题时,可以直接参考,但如果有同样需求或者问题的朋友,你能够通过此文解决一些问题,那也是很好的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/410246.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

On Data Scaling in Masked Image Modelin

论文名称:On Data Scaling in Masked Image Modeling 发表时间:CVPR2023 作者及组织:Zhenda Xie, ZhengZhang, Hu Han等,来自清华,西安交大,微软亚洲研究院。 前言 本文验证SIMMIM无监督预训练方法&#x…

VMware workstation安装debian-12.1.0虚拟机(最小化安装)并配置网络

VMware workstation安装debian-12.1.0虚拟机(最小化安装)并配置网络 Debian 是一个完全自由的操作系统!Debian 有一个由普罗大众组成的社区!该文档适用于在VMware workstation平台安装最小化安装debian-12.1.0虚拟机。 1.安装准…

Pandas实战100例-专栏介绍

Pandas,Python数据科学的心脏,是探索和分析数据世界的强大工具。想象一下,用几行代码就能洞察庞大数据集的秘密,无论是金融市场趋势还是社交媒体动态。 通过Pandas,你可以轻松地整理、清洗、转换数据,将杂…

Softmax回归(多类分类模型)

目录 1.对真实值类别编码:2.预测值:3.目标函数要求:4.使用Softmax模型将输出置信度Oi计算转换为输出匹配概率y^i:5.使用交叉熵作为损失函数:6.代码实现: 1.对真实值类别编码: y为真实值&#xf…

部署YUM仓库及NFS共享存储

引言: 学习YUM 软件仓库,可以完成安装、卸载、自动升级 rpm 软件包等任务,能够自动 查找并解决 rpm 包之间的依赖关系,而无须管理员逐个、手工地去安装每个 rpm 包,使管理员在维护大量 Linux 服务器时更加轻松自如。特…

【IAP】核心开发流程

最近做了IAP U盘升级模块开发,总结下IAP基本开发流程,不深入讨论原理。 详细原理参考 首先需要知道我们需要把之前的APP区域拆一块出来做BOOT升级程序区域。 以STM32F103为例,0x08000000到0x0807FFFF为FLASH空间,即上图代码区域…

软件测试卷王的自述,我难道真的很卷吗?

前言 转眼就到了2024年了,工作这几年我的薪资也从12k涨到了18k,对于工作只有3年多的我来说,还是比较满意的,毕竟一些工作4、5年的可能还没我高。 我可能就是大家说的卷王,感觉自己年轻,所以从早干到晚&am…

龙蜥操作系统上安装MySQL:步骤详解与常见问题解决

目录 博客前言 一.下载MySQL 1.官网下载 2.上传文件到龙蜥操作系统中 ​编辑二.安装MySQL 1.检查操作系统中的默认数据库并移除 2.创建文件夹解压 3.开始安装 4.启动服务 ​编辑 5.登录修改密码,进行授权 三.第三方工具连接(naviact&#xff…

计算机三级(网络技术)——应用题

第一题 61.输出端口S0 (直接连接) RG的输出端口S0与RE的S1接口直接相连构成一个互联网段 对172.0.147.194和172.0.147.193 进行聚合 前三段相同,将第四段分别转换成二进制 11000001 11000010 前6位相同,加上前面三段 共30…

网络安全中的“三高一弱”和“两高一弱”是什么?

大家在一些网络安全检查中,可能经常会遇到“三高一弱”这个说法。那么,三高一弱指的是什么呢? 三高:高危漏洞、高危端口、高风险外连 一弱:弱口令 一共是4个网络安全风险,其中的“高危漏洞、高危端口、弱…

Qt6入门教程 8:信号和槽机制(连接方式)

目录 一.一个信号与槽连接的例子 二.第五个参数 1.Qt::AutoConnection 2.Qt::DirectConnection 3.Qt::QueuedConnection 4.Qt::BlockingQueuedConnection 5.Qt::UniqueConnection 三.信号 四.connect函数原型 五.信号与槽的多种用法 六.槽的属性 一.一个信号与槽连接…

vscode(visual studio code) 免密登陆服务器

1.生成密钥 首先,在本地,打开命令输入框: WinR–>弹出输入框,输入cmd,打开命令框。 然后,在命令框,输入 ssh-keygen -t rsa -C "love"按两次回车键,问你是否重写,选择…