当你在pyqt5界面代码写如下代码时候:
import threading
import time
def run_thread():
while True:
#do something
time.sleep(1)
threading.Thread(target=run_thread).start()
这样绝对不可能卡死,绝对不可能!当你兴高采烈点击按钮时候,结果大失所望,主窗口卡死了!接着你陷入沉思,是不是线程用错了?然后反复排查怎么也没有发现问题,于是你开始陷入这个坑,找不出原因或者要花费更多时间排查,既然这是病(BUG)就得治疗。最终发现是time.sleep的问题,因此有下面解决方法。该方法来自http://t.zoukankan.com/hhh5460-p-5175322.html
这里重新复述,让你知识有所长进
知识点:
1. 将 time.sleep 替换为 QTimer
2. 将 time.sleep 放入到 QThread
3. 使用 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_()
4. 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_())