本文基於:windows 7 + python 3.4
知識點:
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_())