PyQtQThread

Resumo : neste tutorial, você aprenderá como usar o PyQt QThreadpara 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 QThreadclasse 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 .

Loop de eventos PyQt

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 QThreadclasse são as seguintes:

Primeiro, crie uma classe que herde QObjecte transfira as operações de longa duração para essa classe.

class Worker(QObject):
   passLinguagem de código:  Python  ( python )

A razão pela qual subclassificamos a QObjectclasse é 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 QMainWindowou 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'TLinguagem de código:  Python  ( python )

Observe que outra maneira de usar a QThreadclasse é 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:

PyQtQThread

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 Workerclasse herda da QObjectclasse 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 Workerclasse tem dois sinais:

  • progresso
  • concluído

Esses sinais são instâncias da pyqtSignalclasse. Como importamos o pyqtSignalas Signal, podemos usar o Signalem 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 Workerturma 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 do_work()método possui o @ 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 MainWindowclasse:

work_requested = Signal(int)Linguagem de código:  Python  ( python )

Segundo, crie um Workerobjeto 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_requestedsinal do MainWindowcom o do_workmé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 QThreada 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.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *