常见问题
当点击GUI页面控件运行程序时,容易导致程序界面出现无响应状态,甚至卡死状态,当程序运行完成,界面才能够恢复正常响应。
原因分析
当我们启动GUI界面程序时,代码通过运行一个死循环的线程实现程序GUI界面的呈现(可称之为主GUI线程);当我们点击页面控件时,程序控件产生信号,发给槽函数执行,此时代码应该当生成子线程处理程序业务逻辑;当处理完成后,子线终止并销毁,主GUI线程仍保持不变(即页面仍存在);
①当开发者调用槽函数执行时,仍使用主线程处理程序工作逻辑,此时容易导致主线程卡死(表现为界面无响应)
②生成子线程处理程序业务逻辑,但子线程序休眠时采用了time.sleep()方法,此时会导致主线程卡死(表现为界面无响应)
示例1:
此示例代码,犯了两种错误:①使用time模式休眠 ②在主GUI线程中的槽函数运行阻塞性代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import time, sys from PySide6.QtWidgets import *
class Window(QDialog): def __init__(self) -> None: super().__init__() self.sec = 0 self.resize(240,100) self.setWindowTitle("Demo")
btn_start = QPushButton("start", self) btn_stop = QPushButton("stop", self) btn_stop.clicked.connect(self.update) self.sec_label = QLabel(self)
layout = QGridLayout(self) layout.addWidget(btn_start,0,0) layout.addWidget(btn_stop,0,1) layout.addWidget(self.sec_label,1,0,1,2)
def update(self): for i in range(20): time.sleep(1) self.sec += 1 self.sec_label.setText(str(self.sec)) if __name__ == "__main__": app = QApplication(sys.argv) form = Window() form.show() sys.exit(app.exec())
|
解决方案
①开发时,代码具体业务逻辑,交由生成子线程进行处理,当子线程完成工作业务,即将子线终止并销毁;
主GUI线程仅可接受子线程传送过来的数据,并在界面进行展示,不负责任何业务逻辑以及阻塞执行的任务;
②需要子线程进行休眠时,由于python全局锁的问题,切记不可使用python自带time库的sleep()方法,而需要调用thread类自身的sleep()方法。(推荐)
代码示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| import sys from PySide6.QtWidgets import * from PySide6.QtCore import *
class Window(QDialog): def __init__(self) -> None: super().__init__() self.sec = 0 self.resize(240,100) self.setWindowTitle("Demo")
self.btn_start = QPushButton("start", self) self.btn_stop = QPushButton("stop", self) self.sec_label = QLabel(self) layout = QGridLayout(self) layout.addWidget(self.btn_start,0,0) layout.addWidget(self.btn_stop,0,1) layout.addWidget(self.sec_label,1,0,1,2)
self.connect(self.btn_start, SIGNAL("clicked()"), self.update) self.connect(self.btn_stop, SIGNAL("clicked()"), self.stop) self.thread = Worker(self) self.thread.change_singal.connect(self.num_change)
def update(self): self.thread.start_run()
def stop(self): self.thread.stop_run()
def num_change(self,change_num): self.sec_label.setText(str(change_num))
class Worker(QThread): change_singal = Signal(str) def __init__(self, parent=None) -> None: super().__init__(parent) self.sec = 0
def run(self): for i in range(20): self.sleep(1) self.sec += 1 print(self.sec) self.change_singal.emit('{}'.format(self.sec))
def start_run(self): self.start()
def stop_run(self): self.terminate() self.wait()
if __name__ == "__main__": app = QApplication(sys.argv) form = Window() form.show() sys.exit(app.exec())
|
③PyQt程序如要需要使用任务调度框架,注意第三方任务调度框架(如apscheduler),是否会阻塞主线程;
简单任务调度可以考虑框架自带的QTime类;(推荐)
④仍使用time模块的sleep方法,但把它放到另一个线程中;(不推荐;若程序功能业务复杂,使用此方法较为不便)