Resumo : neste tutorial, você aprenderá como usar o PyQt QThread
para criar um aplicativo Qt responsivo.
Introdução à classe PyQt QThread
Se um programa tiver operações de longa duração, ele poderá atrasar por um breve momento. Em alguns casos, o programa fica completamente congelado.
Portanto, ao desenvolver programas PyQt, você deve saber como lidar com essas situações. E para fazer isso, você pode aproveitar a vantagem do threading.
Se você não estiver familiarizado com o conceito de threading , poderá aprender mais sobre ele na série sobre simultaneidade do Python .
Python possui vários módulos para lidar com threads, como threading e concurrent.futures
.
Embora você possa usar esses módulos, o PyQt oferece uma maneira melhor de fazer isso usando a QThread
classe e outras classes.
O thread principal e os threads de trabalho
Os aplicativos Qt são baseados em eventos. Quando você chama o exec()
método, ele inicia um loop de eventos e cria um thread que é chamado de thread principal .
Quaisquer eventos que ocorrem no thread principal são executados de forma síncrona no loop de eventos principal.
Para aproveitar as vantagens do threading, você precisa criar um thread secundário para descarregar as operações de longa duração do thread principal. Os threads secundários são frequentemente chamados de threads de trabalho .
Para se comunicar entre o thread principal e os threads de trabalho, você usa sinais e slots. As etapas para usar a QThread
classe são as seguintes:
Primeiro, crie uma classe que herde QObject
e transfira as operações de longa duração para essa classe.
class Worker(QObject):
pass
Linguagem de código: Python ( python )
A razão pela qual subclassificamos a QObject
classe é que queremos usar o sinal e o slot.
Em seguida, crie um thread de trabalho e um objeto de trabalho a partir do thread principal
self.worker = Worker()
self.worker_thread = QThread()
Linguagem de código: Python ( python )
O self é uma instância do QMainWindow
ou QWidget
.
Em seguida, conecte sinais e slots da classe Worker ao thread principal.
Depois disso, mova o trabalhador para o thread de trabalho chamando o moveToThread()
método do objeto trabalhador:
self.worker.moveToThread(self.worker_thread)
Linguagem de código: Python ( python )
Finalmente, inicie o thread de trabalho:
self.worker_thread.start()
Linguagem de código: Python ( python )
É importante observar que você só deve se comunicar com o trabalhador por meio de sinais e slots. E você não chama nenhum de seus métodos do thread principal. Por exemplo:
self.worker.do_some_work() # DON'T
Linguagem de código: Python ( python )
Observe que outra maneira de usar a QThread
classe é subclassificá-la e substituir o run()
método. No entanto, não é uma forma recomendada. Encontre a resposta detalhada aqui .
Exemplo PyQt QThread
Criaremos um programa simples que usa QThread
:
O programa consiste em uma barra de progresso e um botão . Quando você clica no botão Iniciar, a operação de longa duração será executada em um thread de trabalho e atualizará o progresso de volta para o thread principal por meio de sinais e slots .
Aqui está o programa completo:
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QLabel, QPushButton, QVBoxLayout, QProgressBar
from PyQt6.QtCore import QThread, QObject, pyqtSignal as Signal, pyqtSlot as Slot
import time
class Worker(QObject):
progress = Signal(int)
completed = Signal(int)
@Slot(int)
def do_work(self, n):
for i in range(1, n+1):
time.sleep(1)
self.progress.emit(i)
self.completed.emit(i)
class MainWindow(QMainWindow):
work_requested = Signal(int)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setGeometry(100, 100, 300, 50)
self.setWindowTitle('QThread Demo')
# setup widget
self.widget = QWidget()
layout = QVBoxLayout()
self.widget.setLayout(layout)
self.setCentralWidget(self.widget)
self.progress_bar = QProgressBar(self)
self.progress_bar.setValue(0)
self.btn_start = QPushButton('Start', clicked=self.start)
layout.addWidget(self.progress_bar)
layout.addWidget(self.btn_start)
self.worker = Worker()
self.worker_thread = QThread()
self.worker.progress.connect(self.update_progress)
self.worker.completed.connect(self.complete)
self.work_requested.connect(self.worker.do_work)
# move worker to the worker thread
self.worker.moveToThread(self.worker_thread)
# start the thread
self.worker_thread.start()
# show the window
self.show()
def start(self):
self.btn_start.setEnabled(False)
n = 5
self.progress_bar.setMaximum(n)
self.work_requested.emit(n)
def update_progress(self, v):
self.progress_bar.setValue(v)
def complete(self, v):
self.progress_bar.setValue(v)
self.btn_start.setEnabled(True)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Linguagem de código: Python ( python )
Como funciona.
Definindo a classe trabalhadora
A Worker
classe herda da QObject
classe para poder suportar sinais e slots. Na prática, você move as operações longas para a classe Worker:
class Worker(QObject):
Linguagem de código: Python ( python )
A Worker
classe tem dois sinais:
- progresso
- concluído
Esses sinais são instâncias da pyqtSignal
classe. Como importamos o
as pyqtSignal
Signal
, podemos usar o Signal
em vez disso:
progress = Signal(int)
completed = Signal(int)
Linguagem de código: Python ( python )
Tanto o progresso quanto o sinal concluído aceitam um número inteiro.
A Worker
turma emitirá o sinal de progresso quando uma parte do trabalho for concluída e o sinal de concluído quando o trabalho for concluído.
A classe Work possui o do_work()
método:
@Slot(int)
def do_work(self, n):
for i in range(1, n+1):
time.sleep(1)
self.progress.emit(i)
self.completed.emit(i)
Linguagem de código: Python ( python )
O
método possui o @ do_work()
Slot()
decorador (ou pyqtSlot
). O decorador @ Slot()
transforma o do_work()
método em um slot.
O decorador @ Slot()
é opcional. No entanto, conectar um sinal a um método Python decorado pode ajudar a reduzir o uso de memória e torná-lo um pouco mais rápido.
O do_work()
método aceita um número inteiro. Ele itera em um intervalo começando de 1 até o argumento. Em cada iteração, ele pausa por um segundo usando o time.sleep()
e emite o sinal de progresso com o valor atual usando o emit()
método.
Uma vez finalizado, o do_work()
método emite o sinal concluído com o último valor inteiro do valor.
Comunicação entre o thread principal e o thread de trabalho
Primeiro, crie um sinal na MainWindow
classe:
work_requested = Signal(int)
Linguagem de código: Python ( python )
Segundo, crie um Worker
objeto e um thread de trabalho:
self.worker = Worker()
self.worker_thread = QThread()
Linguagem de código: Python ( python )
Terceiro, conecte o progresso e o sinal concluído do objeto trabalhador com os métodos da janela principal:
self.worker.progress.connect(self.update_progress)
self.worker.completed.connect(self.complete)
Linguagem de código: Python ( python )
Quarto, conecte o work_requested
sinal do MainWindow
com o do_work
método do objeto trabalhador:
self.work_requested.connect(self.worker.do_work)
Linguagem de código: Python ( python )
Quinto, mova o trabalhador para o thread de trabalho chamando o moveToThread()
método:
self.worker.moveToThread(self.worker_thread)
Linguagem de código: Python ( python )
Finalmente, inicie o thread de trabalho:
self.worker_thread.start()
Linguagem de código: Python ( python )
Resumo
- Use
QThread
a classe para criar um thread de trabalho para descarregar uma operação longa do thread principal. - Use sinais e slots para comunicação entre o thread principal e o thread de trabalho.