PyQt在使用time模块时的卡死问题解决方法
程序员文章站
2022-03-01 21:39:09
...
PyQt5 笔记(04):主窗口卡死问题
本文基于:windows 7 + python 3.4
知识点:
-
将 time.sleep 替换为 QTimer
-
将 time.sleep 放入到 QThread
-
使用 QThread 自己的 sleep 方法
我们希望实现一个这样的小程序:
当点击开始按钮的时候,下面的文本标签每隔一秒自动加1。
一、直接用 time.sleep(1)
import time
class TestWindow(QDialog):
def __init__(self):
# ...
btn1.clicked.connect(self.update) # 按钮连接到槽
# ...
def update(self):
for i in range(20):
time.sleep(1) # 每隔一秒
self.sec += 1
self.sec_label.setText(str(self.sec))
看起来没有任何逻辑上的错误。
那就运行一下看看,点击按钮。。。神马情况?主界面卡死了!如图
我猜测这可能与python的GIL问题有关:
1. time库是纯python的,而PyQt的背后是Qt,这是纯C++的。
2. 换句话说,就是time.sleep(1)时,并没有将CPU控制权交还给Qt,从而造成界面卡死
解决这个问题,既然不能用 python 的 time 库,那就用 PyQt 自己的 QTimer 类
二、使用 QTimer 类
class TestWindow(QDialog):
def __init__(self):
# ...
timer = QTimer() # 计时器
timer.timeout.connect(self.update)
btn1.clicked.connect(lambda :timer.start(1000)) # 启动计时器,间隔1秒
btn2.clicked.connect(lambda :timer.stop())
def update(self):
self.sec += 1
self.sec_label.setText(str(self.sec))
再运行一下。。。 OK,搞定!如图:
完整代码:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class TestWindow(QDialog):
def __init__(self):
super().__init__()
self.sec = 0
btn1 = QPushButton("Start", self)
btn2 = QPushButton("Stop", self)
self.sec_label = QLabel(self)
layout = QGridLayout(self)
layout.addWidget(btn1,0,0)
layout.addWidget(btn2,0,1)
layout.addWidget(self.sec_label,1,0,1,2)
timer = QTimer()
timer.timeout.connect(self.update) # 计时器挂接到槽:update
btn1.clicked.connect(lambda :timer.start(1000))
btn2.clicked.connect(lambda :timer.stop())
def update(self):
self.sec += 1
self.sec_label.setText(str(self.sec))
app=QApplication(sys.argv)
form=TestWindow()
form.show()
app.exec_()
三、将 time.sleep 放入到 QThread
解决这个问题的另外一个思路:开一个线程,专门用于计时(即:专门运行 time.sleep)
在 QThread 中使用 time.sleep 和 for 循环,无压力!
当然,线程与主窗口的通信使用了信号/槽。
代码如下:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import time
class TestWindow(QDialog):
def __init__(self):
super().__init__()
btn1 = QPushButton("Start", self)
btn2 = QPushButton("Stop", self)
self.sec_label = QLabel(self)
layout = QGridLayout(self)
layout.addWidget(btn1,0,0)
layout.addWidget(btn2,0,1)
layout.addWidget(self.sec_label,1,0,1,2)
thread = MyThread() # 创建一个线程
thread.sec_changed_signal.connect(self.update) # 线程发过来的信号挂接到槽:update
btn1.clicked.connect(lambda :thread.start())
btn2.clicked.connect(lambda :thread.terminate()) # 线程中止
def update(self, sec):
self.sec_label.setText(str(sec))
class MyThread(QThread):
sec_changed_signal = pyqtSignal(int) # 信号类型:int
def __init__(self, sec=1000, parent=None):
super().__init__(parent)
self.sec = sec # 默认1000秒
def run(self):
for i in range(self.sec):
self.sec_changed_signal.emit(i) #发射信号
time.sleep(1)
app = QApplication(sys.argv)
form = TestWindow()
form.show()
app.exec_()
四. QThread 自身也有一个 sleep 方法
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class Test(QDialog):
def __init__(self,parent=None):
super().__init__(parent)
self.file_list = QListWidget()
self.btn = QPushButton('Start')
layout = QGridLayout(self)
layout.addWidget(self.file_list,0,0,1,2)
layout.addWidget(self.btn,1,1)
self.thread = Worker()
self.thread.file_changed_signal.connect(self.update_file_list)
self.btn.clicked.connect(self.thread_start)
def update_file_list(self, file_inf):
self.file_list.addItem(file_inf)
def thread_start(self):
self.btn.setEnabled(False)
self.thread.start()
class Worker(QThread):
file_changed_signal = pyqtSignal(str) # 信号类型:str
def __init__(self, sec=0, parent=None):
super().__init__(parent)
self.working = True
self.sec = sec
def __del__(self):
self.working = False
self.wait()
def run(self):
while self.working == True:
self.file_changed_signal.emit('当前秒数:{}'.format(self.sec))
self.sleep(1)
self.sec += 1
app = QApplication(sys.argv)
dlg = Test()
dlg.show()
sys.exit(app.exec_())
补:QObject -> moveToThread 方式应用 QThread
from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time
import sys
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int)
@pyqtSlot()
def work(self): # A slot takes no params
for i in range(1, 100):
time.sleep(1)
self.intReady.emit(i)
self.finished.emit()
class Form(QWidget):
def __init__(self):
super().__init__()
self.label = QLabel("0")
# 1 - create Worker and Thread inside the Form
self.worker = Worker() # no parent!
self.thread = QThread() # no parent!
self.worker.intReady.connect(self.updateLabel)
self.worker.moveToThread(self.thread)
self.worker.finished.connect(self.thread.quit)
self.thread.started.connect(self.worker.work)
#self.thread.finished.connect(app.exit)
self.thread.start()
self.initUI()
def initUI(self):
grid = QGridLayout()
self.setLayout(grid)
grid.addWidget(self.label,0,0)
self.move(300, 150)
self.setWindowTitle('thread test')
def updateLabel(self, i):
self.label.setText("{}".format(i))
#print(i)
app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())