鱼C论坛

 找回密码
 立即注册
查看: 3910|回复: 21

[已解决]pyqt5 多线程问题求助

[复制链接]
发表于 2021-2-22 13:02:39 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
两个py文件
一个ui的ui_Widget.py
  1. # -*- coding: utf-8 -*-

  2. # Form implementation generated from reading ui file 'Widget.ui'
  3. #
  4. # Created by: PyQt5 UI code generator 5.15.1
  5. #
  6. # WARNING: Any manual changes made to this file will be lost when pyuic5 is
  7. # run again.  Do not edit this file unless you know what you are doing.


  8. from PyQt5 import QtCore, QtGui, QtWidgets


  9. class Ui_Widget(object):
  10.     def setupUi(self, Widget):
  11.         Widget.setObjectName("Widget")
  12.         Widget.resize(710, 593)
  13.         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
  14.         sizePolicy.setHorizontalStretch(0)
  15.         sizePolicy.setVerticalStretch(0)
  16.         sizePolicy.setHeightForWidth(Widget.sizePolicy().hasHeightForWidth())
  17.         Widget.setSizePolicy(sizePolicy)
  18.         font = QtGui.QFont()
  19.         font.setPointSize(16)
  20.         Widget.setFont(font)
  21.         self._2 = QtWidgets.QVBoxLayout(Widget)
  22.         self._2.setContentsMargins(5, 5, 5, 5)
  23.         self._2.setSpacing(6)
  24.         self._2.setObjectName("_2")
  25.         self.label = QtWidgets.QLabel(Widget)
  26.         self.label.setEnabled(True)
  27.         self.label.setText("")
  28.         self.label.setPixmap(QtGui.QPixmap("22.jpg"))
  29.         self.label.setObjectName("label")
  30.         self._2.addWidget(self.label)
  31.         self.groupBox_2 = QtWidgets.QGroupBox(Widget)
  32.         self.groupBox_2.setObjectName("groupBox_2")
  33.         self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox_2)
  34.         self.verticalLayout.setContentsMargins(9, 9, 9, 9)
  35.         self.verticalLayout.setSpacing(6)
  36.         self.verticalLayout.setObjectName("verticalLayout")
  37.         self.lineEdit = QtWidgets.QLineEdit(self.groupBox_2)
  38.         self.lineEdit.setClearButtonEnabled(True)
  39.         self.lineEdit.setObjectName("lineEdit")
  40.         self.verticalLayout.addWidget(self.lineEdit)
  41.         self._2.addWidget(self.groupBox_2)
  42.         self.horizontalLayout = QtWidgets.QHBoxLayout()
  43.         self.horizontalLayout.setSpacing(4)
  44.         self.horizontalLayout.setObjectName("horizontalLayout")
  45.         self.groupBox = QtWidgets.QGroupBox(Widget)
  46.         self.groupBox.setObjectName("groupBox")
  47.         self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
  48.         self.gridLayout.setContentsMargins(11, 11, 11, 11)
  49.         self.gridLayout.setSpacing(6)
  50.         self.gridLayout.setObjectName("gridLayout")
  51.         self.comboBox = QtWidgets.QComboBox(self.groupBox)
  52.         self.comboBox.setEditable(False)
  53.         self.comboBox.setObjectName("comboBox")
  54.         self.gridLayout.addWidget(self.comboBox, 1, 0, 1, 3)
  55.         self.btnIniItems = QtWidgets.QPushButton(self.groupBox)
  56.         self.btnIniItems.setObjectName("btnIniItems")
  57.         self.gridLayout.addWidget(self.btnIniItems, 0, 0, 1, 1)
  58.         self.btnClearItems = QtWidgets.QPushButton(self.groupBox)
  59.         self.btnClearItems.setObjectName("btnClearItems")
  60.         self.gridLayout.addWidget(self.btnClearItems, 0, 1, 1, 1)
  61.         self.btnOkItems = QtWidgets.QPushButton(self.groupBox)
  62.         self.btnOkItems.setStyleSheet("background-color: rgb(255, 0, 0);\n"
  63. "font: 16pt "MS Shell Dlg 2";")
  64.         self.btnOkItems.setObjectName("btnOkItems")
  65.         self.gridLayout.addWidget(self.btnOkItems, 0, 2, 1, 1)
  66.         self.horizontalLayout.addWidget(self.groupBox)
  67.         self._2.addLayout(self.horizontalLayout)
  68.         self.plainTextEdit = QtWidgets.QPlainTextEdit(Widget)
  69.         self.plainTextEdit.setObjectName("plainTextEdit")
  70.         self._2.addWidget(self.plainTextEdit)

  71.         self.retranslateUi(Widget)
  72.         self.comboBox.setCurrentIndex(-1)
  73.         QtCore.QMetaObject.connectSlotsByName(Widget)

  74.     def retranslateUi(self, Widget):
  75.         _translate = QtCore.QCoreApplication.translate
  76.         Widget.setWindowTitle(_translate("Widget", "Demo3_6 ComboBox"))
  77.         self.groupBox_2.setTitle(_translate("Widget", "选择的工序如下"))
  78.         self.groupBox.setTitle(_translate("Widget", "选择导出数据的工序"))
  79.         self.btnIniItems.setText(_translate("Widget", "初始化列表"))
  80.         self.btnClearItems.setText(_translate("Widget", "清除列表"))
  81.         self.btnOkItems.setText(_translate("Widget", "确定"))
复制代码


第二个py,文件myWidget.py
  1. import sys
  2. from PyQt5.QtWidgets import  QApplication, QWidget
  3. from PyQt5.QtCore import  pyqtSlot
  4. from ui_Widget import Ui_Widget #ui界面
  5. import itp_drawing as drawing #函数1
  6. import itp_bunching as bunching #函数2
  7. import itp_conductor as conductor #函数3
  8. import itp_extruder as extruder #函数4
  9. import itp_rubber as rubber #函数5
  10. import itp_ccv as ccv #函数6

  11. class QmyWidget(QWidget):
  12.    def __init__(self, parent=None):
  13.       super().__init__(parent)   #调用父类构造函数,创建窗体
  14.       self.ui=Ui_Widget()        #创建UI对象
  15.       self.ui.setupUi(self)      #构造UI界面

  16. ##  ==========由connectSlotsByName() 自动连接的槽函数====================        
  17.    def on_btnIniItems_clicked(self):   ##“初始化列表”按钮
  18.       self.ui.comboBox.clear()    #清除列表
  19.       provinces=["拉丝","束丝","导体","挤出","橡胶","ccv"]    #列表数据
  20.       for i in range(len(provinces)):
  21.          self.ui.comboBox.addItem(provinces[i])

  22.    def on_btnClearItems_clicked(self):    ##“清除列表”按钮
  23.       self.ui.comboBox.clear()

  24.    def on_btnOkItems_clicked(self):  ##“确定”按钮
  25.       if self.ui.lineEdit.text().strip()=='拉丝':
  26.          self.ui.plainTextEdit.clear()
  27.          drawing.concat_file()
  28.          self.ui.plainTextEdit.insertPlainText('拉丝数据导出完毕\n==========\n')
  29.       elif self.ui.lineEdit.text().strip()=='束丝':
  30.          self.ui.plainTextEdit.clear()
  31.          bunching.read_bunching()
  32.          self.ui.plainTextEdit.insertPlainText('束丝数据导出完毕\n==========\n')
  33.       elif self.ui.lineEdit.text().strip() == '导体':
  34.          self.ui.plainTextEdit.clear()
  35.          conductor.concat_file()
  36.          self.ui.plainTextEdit.insertPlainText('导体数据导出完毕\n==========\n')
  37.       elif self.ui.lineEdit.text().strip()=='挤出':
  38.          self.ui.plainTextEdit.clear()
  39.          extruder.concat_file()
  40.          self.ui.plainTextEdit.insertPlainText('挤出数据导出完毕\n==========\n')
  41.       elif self.ui.lineEdit.text().strip()=='橡胶':
  42.          self.ui.plainTextEdit.clear()
  43.          rubber.concat_file()
  44.          self.ui.plainTextEdit.insertPlainText('橡胶数据导出完毕\n==========\n')
  45.       elif self.ui.lineEdit.text().strip()=='ccv':
  46.          self.ui.plainTextEdit.clear()
  47.          ccv.concat_file()
  48.          self.ui.plainTextEdit.insertPlainText('CCV数据导出完毕\n==========\n')
  49.       else:
  50.          self.ui.plainTextEdit.clear()
  51.          self.ui.plainTextEdit.insertPlainText('请选择正确的工序!!!\n==========\n')

  52.    @pyqtSlot(str)    ##“简单的ComboBox”的当前项变化
  53.    def on_comboBox_currentIndexChanged(self,curText):
  54.       self.ui.lineEdit.setText(curText)

  55. ##  ===========窗体测试程序 ================================        
  56. if  __name__ == "__main__":         
  57.    app = QApplication(sys.argv)   
  58.    form=QmyWidget()      
  59.    form.show()
  60.    sys.exit(app.exec_())
复制代码


现在的问题是,6个函数,选中某一个后,由于函数读取数据文本比较耗时,导致界面卡。
如何改成多线程模式?
比如把6个函数改成6个checkbutton,选中几个就新开几个线程,且主界面不死。
最佳答案
2021-2-25 18:14:57
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])将dataFrame包在列表内发射出去。在你的myWidget内定义一个接收一个参数a(即将被发射的[dataFrame])的显示tabwiew的方法update_tabview,update_tableview内根据a的值设置tableview。然后将Thread1的实例的signal1信号连接至update_tableview即可。
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2021-2-23 09:16:03 | 显示全部楼层
hrp 发表于 2021-2-22 14:55
用QThread,它的线程实例开始时发射started信号,线程结束后发射finished信号,可以用started和finished信 ...

你的意思是在确认按钮这里,threading开6个线程,然后判断,选中了哪个?然后start选中的线程。是这样吗?
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-2-23 10:57:29 | 显示全部楼层
hrp 发表于 2021-2-23 10:23
不是,是定义一个方法,将6个按钮的点击信号都连接到这个方法(甚至你可以分别定义6个不同的方法,将6个 ...

感谢,我去研究下这个信号和槽的部分。有好的帖子或者demo欢迎分享
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-2-23 16:10:03 | 显示全部楼层
看到有人展示一个这样的,不传参数的
  1. from PyQt5.Qt import (QApplication, QWidget, QPushButton,
  2.                       QThread,QMutex,pyqtSignal)
  3. import sys
  4. import time

  5. qmut_1 = QMutex() # 创建线程锁
  6. qmut_2 = QMutex()
  7. # 继承QThread
  8. class Thread_1(QThread):  # 线程1
  9.     def __init__(self):
  10.         super().__init__()

  11.     def run(self):
  12.         qmut_1.lock() # 加锁
  13.         values = [1, 2, 3, 4, 5]
  14.         for i in values:
  15.             print(i)
  16.             time.sleep(0.5)  # 休眠
  17.         qmut_1.unlock() # 解锁


  18. class Thread_2(QThread):  # 线程2
  19.     _signal =pyqtSignal()
  20.     def __init__(self):
  21.         super().__init__()

  22.     def run(self):
  23.         # qmut_2.lock()  # 加锁
  24.         values = ["a", "b", "c", "d", "e"]
  25.         for i in values:
  26.             print(i)
  27.             time.sleep(0.5)
  28.         # qmut_2.unlock()  # 解锁
  29.         self._signal.emit()


  30. class MyWin(QWidget):
  31.     def __init__(self):
  32.         super().__init__()
  33.         # 按钮初始化
  34.         self.btn_1 = QPushButton('按钮1', self)
  35.         self.btn_1.move(120, 80)
  36.         self.btn_1.clicked.connect(self.click_1)  # 绑定槽函数

  37.         self.btn_2 = QPushButton('按钮2', self)
  38.         self.btn_2.move(120, 120)
  39.         self.btn_2.clicked.connect(self.click_2)  # 绑定槽函数

  40.     def click_1(self):
  41.         self.thread_1 = Thread_1()  # 创建线程
  42.         self.thread_1.start()  # 开始线程

  43.     def click_2(self):
  44.         self.btn_2.setEnabled(False)
  45.         self.thread_2 = Thread_2()
  46.         self.thread_2._signal.connect(self.set_btn)
  47.         self.thread_2.start()

  48.     def set_btn(self):
  49.         self.btn_2.setEnabled(True)


  50. if __name__ == "__main__":
  51.     app = QApplication(sys.argv)
  52.     myshow = MyWin()
  53.     myshow.show()
  54.     sys.exit(app.exec_())
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-2-23 16:12:16 | 显示全部楼层
还有一种这样的
  1. import sys
  2. from PyQt5 import QtCore
  3. from PyQt5.QtWidgets import *
  4. from PyQt5.QtGui import *
  5. from PyQt5.QtCore import *

  6. class Demo(QWidget):
  7.     def __init__(self):
  8.         super().__init__()
  9.         self.setGeometry(100, 50, 500, 400)
  10.         self.setWindowTitle('QThread')

  11.         self.thread = Worker()
  12.         self.list = QListWidget()
  13.         self.btn = QPushButton('开始')
  14.         layout = QGridLayout(self)
  15.         layout.addWidget(self.list,0,0,1,2)
  16.         layout.addWidget(self.btn,1,1)
  17.         self.btn.clicked.connect(self.slotStart)
  18.         self.thread.sinOut.connect(self.slotAdd)

  19.     def slotAdd(self,inf):
  20.         self.list.addItem(inf)
  21.         #实时刷新页面
  22.         QApplication.processEvents()

  23.     def slotStart(self):
  24.         self.btn.setEnabled(False)
  25.         self.thread.start()

  26. class Worker(QThread):
  27.     #自定义信号
  28.     sinOut = pyqtSignal(str)

  29.     def __init__(self):
  30.         super().__init__()
  31.         self.working = True
  32.         self.num = 0

  33.     def __del__(self):
  34.         self.working = False
  35.         self.wait()

  36.     def run(self):
  37.         while self.working == True:
  38.             file_str = 'File index{0}'.format(self.num)
  39.             self.num += 1
  40.             #发射信号
  41.             self.sinOut.emit(file_str)
  42.             #休眠2秒
  43.             self.sleep(2)

  44. if __name__ == "__main__":
  45.     app = QApplication(sys.argv)
  46.     form = Demo()
  47.     form.show()
  48.     sys.exit(app.exec_())
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-2-23 17:21:48 | 显示全部楼层
hrp 发表于 2021-2-23 16:43
qthread自定义信号更方便

信号我像传递的是用pandas库清洗出来的dataframe,我看了emit的几种形式,好像传输不了这种
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-2-25 13:25:49 | 显示全部楼层
hrp 发表于 2021-2-23 17:41
可以发射list等类型的啊,把数据装进列表再发射不就好了

我先写了一个,是combox,选中后,点击按钮,然后开启线程,进行数据清洗。
你说的把dataframe发射出来,应该怎么写呢?list.append?
我没太懂。

目的:
我是想把这个dataframe用model/view模式在tableview上显示出来。
不知道是不是必须的用信号发射出来才能实现。
还是我直接在线程中就把数据显示出来。

你看看我写的,还没发射dataframe的
  1. import sys
  2. from PyQt5.QtWidgets import  QApplication, QWidget
  3. from PyQt5.QtCore import  pyqtSlot, QThread,pyqtSignal
  4. from ui_Widget import Ui_Widget
  5. import itp_drawing as drawing

  6. class Thread1(QThread):
  7.    signal1 = pyqtSignal()
  8.    def __init__(self):
  9.       super(Thread1, self).__init__()

  10.    def run(self):
  11.       df = drawing.concat_file()



  12. class QmyWidget(QWidget):
  13.    def __init__(self, parent=None):
  14.       super().__init__(parent)   #调用父类构造函数,创建窗体
  15.       self.ui=Ui_Widget()        #创建UI对象
  16.       self.ui.setupUi(self)      #构造UI界面


  17. ##  ==========由connectSlotsByName() 自动连接的槽函数====================        
  18.    def on_btnIniItems_clicked(self):   ##“初始化列表”按钮
  19.       self.ui.comboBox.clear()    #清除列表
  20.       provinces=["拉丝","束丝","导体","挤出","橡胶","ccv"]    #列表数据
  21.       for i in range(len(provinces)):
  22.          self.ui.comboBox.addItem(provinces[i])

  23.    def on_btnClearItems_clicked(self):    ##“清除列表”按钮
  24.       self.ui.comboBox.clear()

  25.    @pyqtSlot()
  26.    def on_btnOkItems_clicked(self):  ##“确定”按钮
  27.       self.thread1 = Thread1()
  28.       self.thread1.start()
  29.       
  30.    @pyqtSlot(str)    ##“简单的ComboBox”的当前项变化
  31.    def on_comboBox_currentIndexChanged(self,curText):
  32.       self.ui.lineEdit.setText(curText)

  33. ##  ===========窗体测试程序 ================================        
  34. if  __name__ == "__main__":         
  35.    app = QApplication(sys.argv)
  36.    form=QmyWidget()
  37.    form.show()
  38.    sys.exit(app.exec_())
复制代码


ui.png
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-2-26 09:18:04 | 显示全部楼层
hrp 发表于 2021-2-25 18:14
用信号发射出来不是必须的,但必须是在主线程中更新界面,也就是说你想在子线程中把数据显示出来是行不 ...

小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-3-8 11:42:25 | 显示全部楼层
按照这个,子线程的额数据清洗后,存储在pkl,然后主线程显示表格的时候再读取pkl
ui给子线程传参.jpg
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2026-4-2 00:17

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表