常见问题
当点击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
33import 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
64import 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方法,但把它放到另一个线程中;(不推荐;若程序功能业务复杂,使用此方法较为不便)