PyQt5: QThreadPool vs QThread
當程式執行時間比較長的時候,會出現類似當機整個畫面不能動的情況。在有 GUI(Graphical user interface) 的程式會特別明顯,而且如果因為程式執行間發生錯誤,也會造成整個程式當機。
PyQt 有提供 multi-tasking 的 library,分別是 QThreadPool
跟 QThread
。
QThreadPool
使用 QThreadPool
要先設計一個 QRunnable
的 Object (我看的範例都叫 Worker),裡面放要花很長時間執行的程式。通常會需要設計 Signals 來傳送各種訊息與其他物件溝通 (不知道表達得是否精確,反正我是這麼理解的)。
import sys
import time
from PyQt5.QtCore import QObject, pyqtSignal, QThreadPool, QRunnable
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QWidget, QPushButton, QLabel
class Signals(QObject):
start = pyqtSignal(str) # 開始訊號
running = pyqtSignal(int, str) # 運行中訊號
complete = pyqtSignal(str) # 完成訊號
# pyqtSignal() 可以傳送各類型的變數,也可以同時傳多個變數。
class Worker(QRunnable):
def __init__(self):
super(Worker, self).__init__()
self.signal = Signals()
def run(self):
"""
要花很長時間執行的程式
"""
self.signal.start.emit("start") # 發出開始訊號
for i in range(10):
self.signal.running.emit(i, "running") # 發出運行中訊號
print(i)
time.sleep(1)
self.signal.complete.emit("complete") # 發出完成訊號
class App(QWidget):
def __init__(self):
super(App, self).__init__()
# Layout
layout = QVBoxLayout(self)
self.setLayout(layout)
# 其他物件
self.start_btn = QPushButton("Start")
self.label_information = QLabel("wait to start")
layout.addWidget(self.start_btn)
layout.addWidget(self.label_information)
# 建立 thread pool
self.threadpool = QThreadPool()
self.start_btn.clicked.connect(self.start_btn_clicked)
def start_btn_clicked(self):
# 建立 worker
worker = Worker()
# 連接訊號與 function
worker.signal.start.connect(self.work_start)
worker.signal.running.connect(self.work_running)
worker.signal.complete.connect(self.work_complete)
# 將 worker 放入thread pool 中並開始運行
self.threadpool.start(worker)
def work_start(self, msg):
"""
收到 start 訊號之後要執行的程式
"""
self.label_information.setText(msg)
def work_running(self, i, msg):
"""
收到 running 訊號之後要執行的程式
"""
self.label_information.setText(f"{i}: {msg}")
def work_complete(self, msg):
"""
收到 complete 訊號之後要執行的程式
"""
self.label_information.setText(msg)
if __name__ == "__main__":
app = QApplication(sys.argv)
windows = App()
windows.show()
sys.exit(app.exec_())
要特別注意的是:
Signals()
一定要另外設計成一個 object,要不然會有錯誤。
QThread
QThread
只要設計一個 Worker,裡面包含 pyqtSignal()
,以及要花很多時間執行的程式。
import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QWidget, QPushButton, QLabel
class Worker(QThread):
is_start = pyqtSignal(str) # 開始訊號
running = pyqtSignal(int, str) # 運行中訊號
complete = pyqtSignal(str) # 完成訊號
# pyqtSignal() 可以傳送各類型的變數,也可以同時傳多個變數。
def __init__(self):
super(Worker, self).__init__()
def run(self):
"""
要花很長時間執行的程式
"""
self.is_start.emit("start") # 發出開始訊號
for i in range(10):
self.running.emit(i, "running") # 發出運行中訊號
print(i)
time.sleep(1)
self.complete.emit("complete") # 發出完成訊號
class App(QWidget):
def __init__(self):
super(App, self).__init__()
# Layout
layout = QVBoxLayout(self)
self.setLayout(layout)
# 其他物件
self.start_btn = QPushButton("Start")
self.label_information = QLabel("wait to start")
layout.addWidget(self.start_btn)
layout.addWidget(self.label_information)
# 建立 thread pool
self.start_btn.clicked.connect(self.start_btn_clicked)
def start_btn_clicked(self):
# 建立 worker
self.worker = Worker()
self.worker.is_start.connect(self.work_start)
self.worker.running.connect(self.work_running)
self.worker.complete.connect(self.work_complete)
self.worker.start()
def work_start(self, msg):
"""
收到 start 訊號之後要執行的程式
"""
self.label_information.setText(msg)
def work_running(self, i, msg):
"""
收到 running 訊號之後要執行的程式
"""
self.label_information.setText(f"{i}: {msg}")
def work_complete(self, msg):
"""
收到 complete 訊號之後要執行的程式
"""
self.label_information.setText(msg)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = App()
window.show()
sys.exit(app.exec_())
要特別注意的是:
Worker()
裡面的pyqtSignal()
一定要是 class variable,不能是 instance variable。- 在
App()
裡面呼叫Worker()
時,一定要用self.worker = Worker()
不能用worker = Worker()
。
QRunnable vs QThread
其實我分不太出來這兩個有什麼不一樣。但是在網路上找到的資料是說,QRunnable
功能比較陽春,而QThread
功能比較完整且彈性。如果一般情況,能用 QRunnable
就用 QRunnable
。
參考資料
- https://www.reddit.com/r/learnpython/comments/azh3xu/python3_with_pyqt5_whats_a_good_way_to_implement/
- https://blog.daychen.tw/2017/02/pyqt5-part-4.html
- https://stackoverflow.com/questions/16791824/c-qt-qthread-vs-qrunnable
關鍵字: python, multithreading, pyqt, GUI, 自學, 程式設計, 醫檢師
留言
張貼留言