找回密码
 会员注册
查看: 20|回复: 0

PythonPyQt5——QThread使用方法与代码实践

[复制链接]

4

主题

0

回帖

13

积分

新手上路

积分
13
发表于 2024-9-5 10:21:42 | 显示全部楼层 |阅读模式
QThread在GUI程序中,如果想要让程序执行一项耗时的工作,例如下载文件、I/O存取等,深度学习模型推理,直接在UI线程中进行这些操作会导致整个界面卡住,出现无响应的状态。为了避免这种情况,可以将这些耗时任务放在另一个线程中执行。在PyQt中,可以使用QThread来实现这一点。基本用法在PyQt5中,要使用QThread创建一个线程,需要创建QThread的子类,并重写QThread.run()函数。以下是一个示例,WorkerThread类继承自QThread并重写了run()方法:classWorkerThread(QThread):def__init__(self):super().__init__()defrun(self):#执行耗时任务12345接下来,可以使用QThread.start()来启动线程。在构造WorkerThread后,不会立即执行run()方法,直到调用QThread.start()才开始执行。如果主线程需要等待线程执行完毕,可以使用QThread.wait(),它会等待线程完成才返回。但请注意,如果线程中包含无限循环,QThread.wait()将会无限期等待。下面是一个简单的示例:importsysimporttimefromPyQt5.QtWidgetsimportQApplicationfromPyQt5.QtCoreimportQThreadclassWorkerThread(QThread):def__init__(self):super().__init__()defrun(self):foriinrange(3):time.sleep(1)print('WorkerThread::run'+str(i))if__name__=='__main__':app=QApplication(sys.argv)print('main')work1=WorkerThread()work2=WorkerThread()work1.start()work2.start()work1.wait()work2.wait()print('endofmain')sys.exit(app.exec_())123456789101112131415161718192021222324在这个run函数中,有一个循环执行3次,并在每次循环后休眠1秒,然后输出一条消息。从输出结果可以看出,主线程会等待两个WorkerThread线程都完成后才输出“endofmain”消息,这证明了使用QThread.wait()是有效的。错误堵塞机制在PyQt5开发过程中,新手容易错误地使用线程,导致界面卡住或变黑。例如,一个下载文件的程序,按下按钮后会执行大约10秒的工作。如果在UI线程中直接执行这些操作,会发现整个GUI程序无法进行其他操作,界面会卡住或变黑。正确的做法是在WorkerThread中定义两个信号,分别为trigger和finished。finished信号在工作完成后发送,而trigger信号在执行过程中发送。使用pyqtSignal来定义自定义信号,并指定发送到目标函数的参数类型。例如,trigger=pyqtSignal(str)。整个程序在按下按钮后,会开启另一个线程,每秒更新一次秒数到标签上,通过self.trigger.emit(str(i+1))发射信号并传递第几秒的参数。第5秒时结束线程,并通过self.finished.emit()发射结束信号。下面是一个示例代码:importsysimporttimefromPyQt5.QtWidgetsimportQApplication,QWidget,QVBoxLayout,QLabel,QPushButtonfromPyQt5.QtCoreimportQThread,pyqtSignalclassWorkerThread(QThread):trigger=pyqtSignal(str)finished=pyqtSignal()def__init__(self):super().__init__()defrun(self):foriinrange(5):time.sleep(1)self.trigger.emit(str(i+1))print('WorkerThread::run'+str(i))self.finished.emit()classMyWidget(QWidget):def__init__(self):super().__init__()self.initUI()definitUI(self):self.setWindowTitle('mywindow')self.setGeometry(50,50,200,150)layout=QVBoxLayout()self.setLayout(layout)self.mylabel=QLabel('pressbuttontostartthread',self)layout.addWidget(self.mylabel)self.mybutton=QPushButton('start',self)self.mybutton.clicked.connect(self.startThread)layout.addWidget(self.mybutton)self.work=WorkerThread()defstartThread(self):self.mybutton.setDisabled(True)self.work.start()self.work.trigger.connect(self.updateLabel)self.work.finished.connect(self.threadFinished)self.updateLabel(str(0))defthreadFinished(self):self.mybutton.setDisabled(False)defupdateLabel(self,text):self.mylabel.setText(text)if__name__=='__main__':app=QApplication(sys.argv)w=MyWidget()w.show()sys.exit(app.exec_())123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354结果如下:mainpressbuttontostartthread1234endofmain1234567如果不想在threadFinished函数中只是执行一段代码,可以省略threadFinished函数,改用lambda表达式。例如,将self.mybutton.setDisabled(False)操作写在self.work.finished.connect()中的lambda表达式里。在QWidget里使用QThread在PyQt程序中,主线程就是所说的UI线程,UI线程会处理所有控件的事务。因此,如果有耗时的工作需要执行,通常不会将其放在UI线程中,因为这样做会阻止其他控件的更新,导致界面卡顿或程序无响应。解决这个问题的方法是创建另一个线程来处理这些耗时的工作。在WorkerThread中新增了两个信号,分别命名为trigger和finished。finished信号在工作完成后发送,而trigger信号在执行过程中发送。当需要自定义信号时,使用pyqtSignal来定义要发送到目标函数的函数原型,例如在下面的示例中,trigger=pyqtSignal(str)表示trigger信号会携带一个字符串参数。整个程序的工作流程是:当按下按钮后,会启动另一个线程,这个线程每一秒更新一次秒数到标签上。通过self.trigger.emit(str(i+1))发送trigger信号,并传递当前的秒数作为参数。当到达第5秒时,线程结束,并通过self.finished.emit()发送finished信号。这种设计允许我们在不影响UI响应性的情况下执行长时间的任务,同时还能更新UI显示当前的进度或状态。通过使用信号和槽机制,可以在工作线程和UI线程之间安全地传递信息,确保程序的流畅运行。#!/usr/bin/envpython3#-*-coding:utf-8-*-importsysimporttimefromPyQt5.QtWidgetsimport(QApplication,QWidget,QVBoxLayout,QLabel,QPushButton)fromPyQt5.QtCoreimportQThread,pyqtSignalclassWorkerThread(QThread):trigger=pyqtSignal(str)finished=pyqtSignal()def__init__(self):super().__init__()defrun(self):foriinrange(5):time.sleep(1)self.trigger.emit(str(i+1))print('WorkerThread::run'+str(i))self.finished.emit()classMyWidget(QWidget):def__init__(self):super().__init__()self.initUI()definitUI(self):self.setWindowTitle('mywindow')self.setGeometry(50,50,200,150)layout=QVBoxLayout()self.setLayout(layout)self.mylabel=QLabel('pressbuttontostartthread',self)layout.addWidget(self.mylabel)self.mybutton=QPushButton('start',self)self.mybutton.clicked.connect(self.startThread)layout.addWidget(self.mybutton)self.work=WorkerThread()defstartThread(self):self.mybutton.setDisabled(True)self.work.start()self.work.trigger.connect(self.updateLabel)self.work.finished.connect(self.threadFinished)self.updateLabel(str(0))defthreadFinished(self):self.mybutton.setDisabled(False)defupdateLabel(self,text):self.mylabel.setText(text)if__name__=='__main__':app=QApplication(sys.argv)w=MyWidget()w.show()sys.exit(app.exec_())123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263如果你不希望threadFinished函数仅仅是执行一行代码,而是想去掉threadFinished函数,可以将self.mybutton.setDisabled(False)操作写在self.work.finished.connect()里的lambda表达式中。以下是使用lambda表达式的示例:defstartThread(self):self.mybutton.setDisabled(True)self.work.start()self.work.trigger.connect(self.updateLabel)#self.work.finished.connect(self.threadFinished)self.work.finished.connect(lambda:self.mybutton.setDisabled(False))self.updateLabel(str(0))#defthreadFinished(self):#self.mybutton.setDisabled(False)defupdateLabel(self,text):self.mylabel.setText(text)12345678910111213这样,当finished信号被触发时,lambda表达式中的代码将会执行,即启用按钮。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-7 05:54 , Processed in 0.575849 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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