pyqt5 多线程问题求助
两个py文件一个ui的ui_Widget.py
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'Widget.ui'
#
# Created by: PyQt5 UI code generator 5.15.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Widget(object):
def setupUi(self, Widget):
Widget.setObjectName("Widget")
Widget.resize(710, 593)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(Widget.sizePolicy().hasHeightForWidth())
Widget.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setPointSize(16)
Widget.setFont(font)
self._2 = QtWidgets.QVBoxLayout(Widget)
self._2.setContentsMargins(5, 5, 5, 5)
self._2.setSpacing(6)
self._2.setObjectName("_2")
self.label = QtWidgets.QLabel(Widget)
self.label.setEnabled(True)
self.label.setText("")
self.label.setPixmap(QtGui.QPixmap("22.jpg"))
self.label.setObjectName("label")
self._2.addWidget(self.label)
self.groupBox_2 = QtWidgets.QGroupBox(Widget)
self.groupBox_2.setObjectName("groupBox_2")
self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox_2)
self.verticalLayout.setContentsMargins(9, 9, 9, 9)
self.verticalLayout.setSpacing(6)
self.verticalLayout.setObjectName("verticalLayout")
self.lineEdit = QtWidgets.QLineEdit(self.groupBox_2)
self.lineEdit.setClearButtonEnabled(True)
self.lineEdit.setObjectName("lineEdit")
self.verticalLayout.addWidget(self.lineEdit)
self._2.addWidget(self.groupBox_2)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSpacing(4)
self.horizontalLayout.setObjectName("horizontalLayout")
self.groupBox = QtWidgets.QGroupBox(Widget)
self.groupBox.setObjectName("groupBox")
self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
self.gridLayout.setContentsMargins(11, 11, 11, 11)
self.gridLayout.setSpacing(6)
self.gridLayout.setObjectName("gridLayout")
self.comboBox = QtWidgets.QComboBox(self.groupBox)
self.comboBox.setEditable(False)
self.comboBox.setObjectName("comboBox")
self.gridLayout.addWidget(self.comboBox, 1, 0, 1, 3)
self.btnIniItems = QtWidgets.QPushButton(self.groupBox)
self.btnIniItems.setObjectName("btnIniItems")
self.gridLayout.addWidget(self.btnIniItems, 0, 0, 1, 1)
self.btnClearItems = QtWidgets.QPushButton(self.groupBox)
self.btnClearItems.setObjectName("btnClearItems")
self.gridLayout.addWidget(self.btnClearItems, 0, 1, 1, 1)
self.btnOkItems = QtWidgets.QPushButton(self.groupBox)
self.btnOkItems.setStyleSheet("background-color: rgb(255, 0, 0);\n"
"font: 16pt \"MS Shell Dlg 2\";")
self.btnOkItems.setObjectName("btnOkItems")
self.gridLayout.addWidget(self.btnOkItems, 0, 2, 1, 1)
self.horizontalLayout.addWidget(self.groupBox)
self._2.addLayout(self.horizontalLayout)
self.plainTextEdit = QtWidgets.QPlainTextEdit(Widget)
self.plainTextEdit.setObjectName("plainTextEdit")
self._2.addWidget(self.plainTextEdit)
self.retranslateUi(Widget)
self.comboBox.setCurrentIndex(-1)
QtCore.QMetaObject.connectSlotsByName(Widget)
def retranslateUi(self, Widget):
_translate = QtCore.QCoreApplication.translate
Widget.setWindowTitle(_translate("Widget", "Demo3_6 ComboBox"))
self.groupBox_2.setTitle(_translate("Widget", "选择的工序如下"))
self.groupBox.setTitle(_translate("Widget", "选择导出数据的工序"))
self.btnIniItems.setText(_translate("Widget", "初始化列表"))
self.btnClearItems.setText(_translate("Widget", "清除列表"))
self.btnOkItems.setText(_translate("Widget", "确定"))
第二个py,文件myWidget.py
import sys
from PyQt5.QtWidgets importQApplication, QWidget
from PyQt5.QtCore importpyqtSlot
from ui_Widget import Ui_Widget #ui界面
import itp_drawing as drawing #函数1
import itp_bunching as bunching #函数2
import itp_conductor as conductor #函数3
import itp_extruder as extruder #函数4
import itp_rubber as rubber #函数5
import itp_ccv as ccv #函数6
class QmyWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent) #调用父类构造函数,创建窗体
self.ui=Ui_Widget() #创建UI对象
self.ui.setupUi(self) #构造UI界面
##==========由connectSlotsByName() 自动连接的槽函数====================
def on_btnIniItems_clicked(self): ##“初始化列表”按钮
self.ui.comboBox.clear() #清除列表
provinces=["拉丝","束丝","导体","挤出","橡胶","ccv"] #列表数据
for i in range(len(provinces)):
self.ui.comboBox.addItem(provinces)
def on_btnClearItems_clicked(self): ##“清除列表”按钮
self.ui.comboBox.clear()
def on_btnOkItems_clicked(self):##“确定”按钮
if self.ui.lineEdit.text().strip()=='拉丝':
self.ui.plainTextEdit.clear()
drawing.concat_file()
self.ui.plainTextEdit.insertPlainText('拉丝数据导出完毕\n==========\n')
elif self.ui.lineEdit.text().strip()=='束丝':
self.ui.plainTextEdit.clear()
bunching.read_bunching()
self.ui.plainTextEdit.insertPlainText('束丝数据导出完毕\n==========\n')
elif self.ui.lineEdit.text().strip() == '导体':
self.ui.plainTextEdit.clear()
conductor.concat_file()
self.ui.plainTextEdit.insertPlainText('导体数据导出完毕\n==========\n')
elif self.ui.lineEdit.text().strip()=='挤出':
self.ui.plainTextEdit.clear()
extruder.concat_file()
self.ui.plainTextEdit.insertPlainText('挤出数据导出完毕\n==========\n')
elif self.ui.lineEdit.text().strip()=='橡胶':
self.ui.plainTextEdit.clear()
rubber.concat_file()
self.ui.plainTextEdit.insertPlainText('橡胶数据导出完毕\n==========\n')
elif self.ui.lineEdit.text().strip()=='ccv':
self.ui.plainTextEdit.clear()
ccv.concat_file()
self.ui.plainTextEdit.insertPlainText('CCV数据导出完毕\n==========\n')
else:
self.ui.plainTextEdit.clear()
self.ui.plainTextEdit.insertPlainText('请选择正确的工序!!!\n==========\n')
@pyqtSlot(str) ##“简单的ComboBox”的当前项变化
def on_comboBox_currentIndexChanged(self,curText):
self.ui.lineEdit.setText(curText)
##===========窗体测试程序 ================================
if__name__ == "__main__":
app = QApplication(sys.argv)
form=QmyWidget()
form.show()
sys.exit(app.exec_())
现在的问题是,6个函数,选中某一个后,由于函数读取数据文本比较耗时,导致界面卡。
如何改成多线程模式?
比如把6个函数改成6个checkbutton,选中几个就新开几个线程,且主界面不死。
用QThread,它的线程实例开始时发射started信号,线程结束后发射finished信号,可以用started和finished信号触发槽函数来更新界面。
为什么不用threading?因为子线程不能直接更新QT界面,只能用QT的信号机制来更新界面。
唯一要注意的只有QThread不能赋值给函数局部变量,应该赋值给实例属性或其他生命周期足够长的变量,否则函数一结束线程也将跟着被结束。 hrp 发表于 2021-2-22 14:55
用QThread,它的线程实例开始时发射started信号,线程结束后发射finished信号,可以用started和finished信 ...
你的意思是在确认按钮这里,threading开6个线程,然后判断,选中了哪个?然后start选中的线程。是这样吗? rsj0315 发表于 2021-2-23 09:16
你的意思是在确认按钮这里,threading开6个线程,然后判断,选中了哪个?然后start选中的线程。是这样吗 ...
不是,是定义一个方法,将6个按钮的点击信号都连接到这个方法(甚至你可以分别定义6个不同的方法,将6个按钮的点击信号分别连接到这6个方法)。
方法的内部,先判断是哪个按钮发射的点击信号(用sender获取信号的发射者),再根据不同按钮创建不同任务的线程,线程中处理数据,把结果赋值给一个实例属性a,线程的started和finished信号分别连接线程开始(如果有需要)和结束要执行的方法f,f方法根据a储存的结果更新界面(比如线程结束信号可以连接在界面显示表格的方法),这就实现了线程结束后自动更新界面。 hrp 发表于 2021-2-23 10:23
不是,是定义一个方法,将6个按钮的点击信号都连接到这个方法(甚至你可以分别定义6个不同的方法,将6个 ...
你的思路很清晰啊,很好! hrp 发表于 2021-2-23 10:23
不是,是定义一个方法,将6个按钮的点击信号都连接到这个方法(甚至你可以分别定义6个不同的方法,将6个 ...
感谢,我去研究下这个信号和槽的部分。有好的帖子或者demo欢迎分享 本帖最后由 hrp 于 2021-2-23 11:55 编辑
rsj0315 发表于 2021-2-23 10:57
感谢,我去研究下这个信号和槽的部分。有好的帖子或者demo欢迎分享
我自己写的 AwesomePyKit 也用到多线程,可以看看,代码写的很烂还没时间重构。{:10_245:}
QThread的使用:在RunPyKit.py中搜索NewTask(继承自QThread)就可以了。
NewTask的定义:在library/libm.py里面。 Cool_Breeze 发表于 2021-2-23 10:36
你的思路很清晰啊,很好!
之前写pykit的时候苦于在子线程中无法更新主界面,然后百度说需要通过信号更新主界面,最后摸索出来的办法{:10_250:} 看到有人展示一个这样的,不传参数的
from PyQt5.Qt import (QApplication, QWidget, QPushButton,
QThread,QMutex,pyqtSignal)
import sys
import time
qmut_1 = QMutex() # 创建线程锁
qmut_2 = QMutex()
# 继承QThread
class Thread_1(QThread):# 线程1
def __init__(self):
super().__init__()
def run(self):
qmut_1.lock() # 加锁
values =
for i in values:
print(i)
time.sleep(0.5)# 休眠
qmut_1.unlock() # 解锁
class Thread_2(QThread):# 线程2
_signal =pyqtSignal()
def __init__(self):
super().__init__()
def run(self):
# qmut_2.lock()# 加锁
values = ["a", "b", "c", "d", "e"]
for i in values:
print(i)
time.sleep(0.5)
# qmut_2.unlock()# 解锁
self._signal.emit()
class MyWin(QWidget):
def __init__(self):
super().__init__()
# 按钮初始化
self.btn_1 = QPushButton('按钮1', self)
self.btn_1.move(120, 80)
self.btn_1.clicked.connect(self.click_1)# 绑定槽函数
self.btn_2 = QPushButton('按钮2', self)
self.btn_2.move(120, 120)
self.btn_2.clicked.connect(self.click_2)# 绑定槽函数
def click_1(self):
self.thread_1 = Thread_1()# 创建线程
self.thread_1.start()# 开始线程
def click_2(self):
self.btn_2.setEnabled(False)
self.thread_2 = Thread_2()
self.thread_2._signal.connect(self.set_btn)
self.thread_2.start()
def set_btn(self):
self.btn_2.setEnabled(True)
if __name__ == "__main__":
app = QApplication(sys.argv)
myshow = MyWin()
myshow.show()
sys.exit(app.exec_()) 还有一种这样的import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class Demo(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(100, 50, 500, 400)
self.setWindowTitle('QThread')
self.thread = Worker()
self.list = QListWidget()
self.btn = QPushButton('开始')
layout = QGridLayout(self)
layout.addWidget(self.list,0,0,1,2)
layout.addWidget(self.btn,1,1)
self.btn.clicked.connect(self.slotStart)
self.thread.sinOut.connect(self.slotAdd)
def slotAdd(self,inf):
self.list.addItem(inf)
#实时刷新页面
QApplication.processEvents()
def slotStart(self):
self.btn.setEnabled(False)
self.thread.start()
class Worker(QThread):
#自定义信号
sinOut = pyqtSignal(str)
def __init__(self):
super().__init__()
self.working = True
self.num = 0
def __del__(self):
self.working = False
self.wait()
def run(self):
while self.working == True:
file_str = 'File index{0}'.format(self.num)
self.num += 1
#发射信号
self.sinOut.emit(file_str)
#休眠2秒
self.sleep(2)
if __name__ == "__main__":
app = QApplication(sys.argv)
form = Demo()
form.show()
sys.exit(app.exec_()) qthread自定义信号更方便 hrp 发表于 2021-2-23 16:43
qthread自定义信号更方便
信号我像传递的是用pandas库清洗出来的dataframe,我看了emit的几种形式,好像传输不了这种 rsj0315 发表于 2021-2-23 17:21
信号我像传递的是用pandas库清洗出来的dataframe,我看了emit的几种形式,好像传输不了这种
可以发射list等类型的啊,把数据装进列表再发射不就好了 hrp 发表于 2021-2-23 17:41
可以发射list等类型的啊,把数据装进列表再发射不就好了
我先写了一个,是combox,选中后,点击按钮,然后开启线程,进行数据清洗。
你说的把dataframe发射出来,应该怎么写呢?list.append?
我没太懂。
目的:
我是想把这个dataframe用model/view模式在tableview上显示出来。
不知道是不是必须的用信号发射出来才能实现。
还是我直接在线程中就把数据显示出来。
你看看我写的,还没发射dataframe的
import sys
from PyQt5.QtWidgets importQApplication, QWidget
from PyQt5.QtCore importpyqtSlot, QThread,pyqtSignal
from ui_Widget import Ui_Widget
import itp_drawing as drawing
class Thread1(QThread):
signal1 = pyqtSignal()
def __init__(self):
super(Thread1, self).__init__()
def run(self):
df = drawing.concat_file()
class QmyWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent) #调用父类构造函数,创建窗体
self.ui=Ui_Widget() #创建UI对象
self.ui.setupUi(self) #构造UI界面
##==========由connectSlotsByName() 自动连接的槽函数====================
def on_btnIniItems_clicked(self): ##“初始化列表”按钮
self.ui.comboBox.clear() #清除列表
provinces=["拉丝","束丝","导体","挤出","橡胶","ccv"] #列表数据
for i in range(len(provinces)):
self.ui.comboBox.addItem(provinces)
def on_btnClearItems_clicked(self): ##“清除列表”按钮
self.ui.comboBox.clear()
@pyqtSlot()
def on_btnOkItems_clicked(self):##“确定”按钮
self.thread1 = Thread1()
self.thread1.start()
@pyqtSlot(str) ##“简单的ComboBox”的当前项变化
def on_comboBox_currentIndexChanged(self,curText):
self.ui.lineEdit.setText(curText)
##===========窗体测试程序 ================================
if__name__ == "__main__":
app = QApplication(sys.argv)
form=QmyWidget()
form.show()
sys.exit(app.exec_())
rsj0315 发表于 2021-2-25 13:25
我先写了一个,是combox,选中后,点击按钮,然后开启线程,进行数据清洗。
你说的把dataframe发射出来 ...
不知道是不是必须的用信号发射出来才能实现。
还是我直接在线程中就把数据显示出来。
用信号发射出来不是必须的,但必须是在主线程中更新界面,也就是说你想在子线程中把数据显示出来是行不通的。
还是前面说过的两个方法:
1,类初始化时设置一个属性比如self.whatever = None。定义例如名为update_tableview的方法,方法内根据self.whatever的值设置tabwiew。定义一个清洗数据的方法getdata,方法内清洗数据并将最终结果赋值给self.whatever。实例化一个线程threadx(getdata),先将其finished信号连接至update_tableview,然后可以threadx.start(),当线程结束后,self.whatever的值已经变为清洗后的数据了,然后threadx的finished信号会触发update_tableview,update_tableview会根据self.whatever的值显示tableview。
2.在你的Thread1类中定义一个list类型的信号signal1=pyqtSignal(list),在处理完结果后,signal1.emit()将dataFrame包在列表内发射出去。在你的myWidget内定义一个接收一个参数a(即将被发射的)的显示tabwiew的方法update_tabview,update_tableview内根据a的值设置tableview。然后将Thread1的实例的signal1信号连接至update_tableview即可。 hrp 发表于 2021-2-25 18:14
用信号发射出来不是必须的,但必须是在主线程中更新界面,也就是说你想在子线程中把数据显示出来是行不 ...
赞 关注,还看不懂,等看懂了再来 按照这个,子线程的额数据清洗后,存储在pkl,然后主线程显示表格的时候再读取pkl 转一下大佬的代码,有个 @hrp
NewTask定义
from PyQt5.QtCore import QThread
class NewTask(QThread):
def __init__(self, target, args=tuple()):
super().__init__()
self._args = args
self._target = target
def run(self):
self._target(*self._args)
def __repr__(self):
return f"{self._target} with args:{self._args}"
__str__ = __repr__
def at_start(self, *callable_objs):
for cab in callable_objs:
if callable(cab):
self.started.connect(cab)
def at_finish(self, *callable_objs):
for cab in callable_objs:
if callable(cab):
self.finished.connect(cab)
应用实例
def do_install():
for name, code in loop_install(
cur_env,
package_to_be_installed,
pre=install_pre,
user=user,
index_url=index_url,
):
item = self.cur_pkgs_info.setdefault(name, ["", "", ""])
if not item:
item = "- N/A -"
item = "安装成功" if code else "安装失败"
thread_install_pkgs = NewTask(do_install)
thread_install_pkgs.at_start(
self.lock_widgets,
lambda: self.show_loading("正在安装,请稍候..."),
)
thread_install_pkgs.at_finish(
self.table_widget_pkgs_info_update,
self.hide_loading,
self.release_widgets,
)
thread_install_pkgs.start()
self.thread_repo.put(thread_install_pkgs, 0)
进程池定义
from PyQt5.QtCore import QMutex
class ThreadRepo:
def __init__(self, interval):
"""interval: 清理已结束线程的时间间隔,单位毫秒。"""
self._thread_repo = []
self._timer_clths = QTimer()
self._mutex = QMutex()
self._timer_clths.timeout.connect(self.clean)
self._timer_clths.start(interval)
self._flag_cleaning = False
def put(self, threadhandle, level=0):
"""将(线程句柄、重要等级)元组加入线程仓库。"""
self._mutex.lock()
self._thread_repo.append((threadhandle, level))
self._mutex.unlock()
def clean(self):
"""清除已结束的线程。"""
if self._flag_cleaning:
return
self._mutex.lock()
index = 0
self._flag_cleaning = True
while index < len(self._thread_repo):
if self._thread_repo.isRunning():
index += 1
continue
del self._thread_repo
self._flag_cleaning = False
self._mutex.unlock()
def stop_all(self):
"""
按线程重要等级退出线程。
0级:重要,安全退出;
1级:不重要,立即退出;
其他:未知等级,安全退出。
"""
for thread, level in self._thread_repo:
if level == 0:
thread.quit()
elif level == 1:
thread.terminate()
else:
thread.quit()
def kill_all(self):
"""立即终止所有线程。"""
for thread, _ in self._thread_repo:
thread.terminate()
def is_empty(self):
"""返回线程仓库是否为空。"""
return not self._thread_repo
均转自AwesomePyKit项目
开源精神!
Salute! 我有一个大胆的想法 :之前的Threading线程结果放到queue里面 然后用Qthread 来读取这个queue里面的东西显示,也就是说 该 Qthread只用来做显示结果用 不参与数据处理过程 (我想到了Iphone系统 不论程序怎么卡 它系统都不会卡)主要是为了实现显示进程和算法进程的互不干扰@hrp 你觉得这个想法怎么样
页:
[1]
2