25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
PyQt QThreadPool
Résumé : dans ce tutoriel, vous apprendrez à créer une application multithreading PyQt qui utilise
et classe. QThreadPool QRunnable
Introduction aux classes QThreadPool & QRunnable
La classe vous permet de décharger une tâche de longue durée vers un thread de travail pour
rendre l’application plus réactive. La classe QThread fonctionne correctement si l’application
comporte quelques threads de travail. QThread ([Link]
Un programme multithread est efficace lorsqu’il possède un nombre d’objets correspondant au
nombre de cœurs de processeur. QThread
En outre, la création de threads est assez coûteuse en termes de ressources informatiques. Par
conséquent, le programme doit réutiliser autant que possible les threads créés.
L’utilisation de la classe pour gérer les threads de travail présente donc deux défis principaux
: QThread
Déterminez le nombre idéal de threads pour l’application en fonction du nombre de cœurs de
processeur.
Réutilisez et recyclez les fils autant que possible.
Heureusement, PyQt a une classe qui résout ces défis pour vous. La classe est souvent utilisée avec
la classe. QThreadPool QThreadPool QRunnable
La classe représente une tâche que vous souhaitez exécuter dans un thread de
travail. QRunnable
Le exécute un objet, gère et recycle automatiquement les threads. QThreadPool QRunnable
Chaque application Qt possède un objet global accessible via la méthode statique de la
classe. QThreadPool globalInstance() QThreadPool
Pour utiliser les classes and, procédez comme suit : QThreadPool QRunnable
[Link] 1/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
Tout d’abord, créez une classe qui hérite ([Link] de la
classe et remplacez la méthode : QRunnable run()
class Worker(QRunnable):
@Slot()
def run(self):
# place a long-running task here
pass
Ensuite, accédez au pool de threads à partir de la fenêtre principale et démarrez les threads de
travail :
class MainWindow(QMainWindow):
# other methods
# ...
def start(self):
""" Create and execute worker threads
"""
pool = [Link]()
for _ in range(1, 100):
[Link](Worker())
Pour mettre à jour la progression du collaborateur vers le thread principal, vous utilisez des signaux
et des emplacements. Cependant, le ne prend pas en charge le signal. QRunnable
Par conséquent, vous devez définir une classe distincte qui hérite de la et utilise cette classe dans la
classe Worker. Voici les étapes : QObject
Tout d’abord, définissez la classe qui sous-classe la classe : Signals QObject
class Signals(QObject):
completed = Signal()
Dans la classe, nous définissons un signal appelé . Notez que vous pouvez définir autant de signaux
que nécessaire. Signals completed
[Link] 2/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
Deuxièmement, émettez le signal lorsque le travail est terminé dans la classe: completed Worker
class Runnable(QRunnable):
def __init__(self):
super().__init__()
[Link] = Signals()
@Slot()
def run(self):
# long running task
# ...
# emit the completed signal
[Link]()
Troisièmement, connectez le signal du thread de production à un emplacement de la fenêtre
principale avant de soumettre le travailleur au pool :
class MainWindow(QMainWindow):
# other methods
# ...
def start(self):
""" Create and execute worker threads
"""
pool = [Link]()
for _ in range(1, 100):
worker = Worker()
[Link]([Link])
[Link](worker)
def update(self):
# update the worker
pass
Exemple QThreadPool
[Link] 3/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
L’exemple suivant illustre l’utilisation de la classe and : QThreadPool QRunnable
import sys
import time
from [Link] import QApplication, QMainWindow, QPushButton, QGridLayou
from [Link] import QRunnable, QObject, QThreadPool, pyqtSignal as Signal
class Signals(QObject):
started = Signal(int)
completed = Signal(int)
class Worker(QRunnable):
def __init__(self, n):
super().__init__()
self.n = n
[Link] = Signals()
@Slot()
def run(self):
[Link](self.n)
[Link](self.n*1.1)
[Link](self.n)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
[Link]('QThreadPool Demo')
self.job_count = 10
self.comleted_jobs = []
widget = QWidget()
[Link] 4/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
[Link](QGridLayout())
[Link](widget)
self.btn_start = QPushButton('Start', clicked=self.start_jobs)
self.progress_bar = QProgressBar(minimum=0, maximum=self.job_count)
[Link] = QListWidget()
[Link]().addWidget([Link], 0, 0, 1, 2)
[Link]().addWidget(self.progress_bar, 1, 0)
[Link]().addWidget(self.btn_start, 1, 1)
[Link]()
def start_jobs(self):
[Link]()
pool = [Link]()
for i in range(1, self.job_count+1):
worker = Worker(i)
[Link]([Link])
[Link]([Link])
[Link](worker)
def restart(self):
self.progress_bar.setValue(0)
self.comleted_jobs = []
self.btn_start.setEnabled(False)
def start(self, n):
[Link](f'Job #{n} started...')
def complete(self, n):
[Link](f'Job #{n} completed.')
self.comleted_jobs.append(n)
self.progress_bar.setValue(len(self.comleted_jobs))
if len(self.comleted_jobs) == self.job_count:
[Link] 5/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
self.btn_start.setEnabled(True)
if __name__ == '__main__':
app = QApplication([Link])
window = MainWindow()
[Link]([Link]())
def complete(self, n):
[Link](f'Job #{n} completed.')
self.comleted_jobs.append(n)
self.progress_bar.setValue(len(self.comleted_jobs))
if len(self.comleted_jobs) == self.job_count:
self.btn_start.setEnabled(True)
Sortie:
Classe Signals
[Link] 6/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
Définissez la classe Signals qui hérite de la classe pour prendre en charge les signaux. Dans la
classe, nous définissons deux signaux : QObject Signals
Le signal sera émis lorsqu’un travailleur est démarré. started
Le signal sera émis lorsqu’un travailleur aura terminé. completed
Les deux signaux acceptent un entier qui identifie le numéro de tâche :
class Signals(QObject):
started = Signal(int)
completed = Signal(int)
Catégorie ouvrière
La classe hérite de la classe. La classe représente une tâche de longue durée que nous déchargeons
vers un thread de travail : Worker QRunnable Worker
class Worker(QRunnable):
def __init__(self, n):
super().__init__()
self.n = n
[Link] = Signals()
@Slot()
def run(self):
[Link](self.n)
[Link](self.n*1.1)
[Link](self.n)
Tout d’abord, initialisez le numéro de tâche (n) et l’objet Signals dans la méthode. __init__()
Deuxièmement, remplacez la méthode de la classe. Pour simuler une tâche de longue durée, nous
utilisons la fonction du module de temps. Avant de démarrer la minuterie, nous émettons le signal
de démarrage; Une fois la minuterie terminée, nous émettons le signal
terminé. run() QRunnable sleep()
[Link] 7/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
MainWindow, classe
La classe définit le pour l’application : MainWindow UI
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
[Link]('QThreadPool Demo')
self.comleted_jobs = []
self.job_count = 10
widget = QWidget()
[Link](QGridLayout())
[Link](widget)
self.btn_start = QPushButton('Start', clicked=self.start_jobs)
self.progress_bar = QProgressBar(minimum=0, maximum=self.job_count)
[Link] = QListWidget()
[Link]().addWidget([Link], 0, 0, 1, 2)
[Link]().addWidget(self.progress_bar, 1, 0)
[Link]().addWidget(self.btn_start, 1, 1)
[Link]()
def start_jobs(self):
[Link]()
pool = [Link]()
for i in range(1, self.job_count+1):
runnable = Worker(i)
[Link]([Link])
[Link]([Link])
[Link](runnable)
[Link] 8/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
def restart(self):
self.progress_bar.setValue(0)
self.comleted_jobs = []
self.btn_start.setEnabled(False)
def start(self, n):
[Link](f'Job #{n} started...')
def complete(self, n):
[Link](f'Job #{n} completed.')
self.comleted_jobs.append(n)
self.progress_bar.setValue(len(self.comleted_jobs))
if len(self.comleted_jobs) == self.job_count:
self.btn_start.setEnabled(True)
Tout d’abord, initialisez le nombre de tâches () et listez dans la méthode de la classe
: job_count completed_jobs __init__() MainWindow
self.job_count = 10
self.comleted_jobs = []
Ensuite, définissez la méthode qui sera exécutée lorsque l’utilisateur cliquera sur le bouton
Démarrer : start_jobs()
def start_jobs(self):
[Link]()
pool = [Link]()
for i in range(1, self.job_count+1):
worker = Worker(i)
[Link]([Link])
[Link]([Link])
[Link](worker)
[Link] 9/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
Le réinitialise le , met à zéro la barre de progression et désactive le bouton de démarrage
: restart() completed_jobs
def restart(self):
self.progress_bar.setValue(0)
self.comleted_jobs = []
self.btn_start.setEnabled(False)
Pour obtenir l’objet, nous utilisons la classe : QThreadPool globalInstance() QThreadPool
pool = [Link]()
Nous créons un certain nombre de travailleurs, connectons leurs signaux aux méthodes de la classe
et démarrons des threads de travail à l’aide de la méthode de
l’objet. MainWindow start() QThreadPool
La méthode ajoute le message qui démarre un thread de travail au : start() QListWidget
def start(self, n):
[Link](f'Job #{n} started...')
La méthode s’exécute chaque fois qu’un thread de production est terminé. Il ajoute un message au
, met à jour la barre de progression et active le bouton Démarrer si tous les threads de travail sont
terminés : completed() QListWidget
def complete(self, n):
[Link](f'Job #{n} completed.')
self.comleted_jobs.append(n)
self.progress_bar.setValue(len(self.comleted_jobs))
if len(self.comleted_jobs) == self.job_count:
self.btn_start.setEnabled(True)
Utiliser QThreadPool pour obtenir les cours des actions
[Link] 10/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
Le programme de cotation en bourse suivant lit les symboles boursiers du fichier et utilise pour
obtenir les prix des actions sur le site Web de Yahoo Finance: [Link] QThreadPool
Programme de cotation en bourse :
import sys
from pathlib import Path
from [Link] import QRunnable, Qt, QObject, QThreadPool, pyqtSignal as Si
from [Link] import QApplication, QMainWindow, QPushButton, QWidget,
from [Link] import QIcon
from lxml import html
import requests
class Signals(QObject):
completed = Signal(dict)
[Link] 11/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
class Stock(QRunnable):
BASE_URL = '[Link]
def __init__(self, symbol):
super().__init__()
[Link] = symbol
[Link] = Signals()
@Slot()
def run(self):
stock_url = f'{self.BASE_URL}{[Link]}'
response = [Link](stock_url)
if response.status_code != 200:
[Link]({'symbol': [Link], 'price': 'N/A'}
return
tree = [Link]([Link])
price_text = [Link](
'//*[@id="quote-header-info"]/div[3]/div[1]/div[1]/fin-streamer[1]
)
if not price_text:
[Link]({'symbol': [Link], 'price': 'N/A'}
return
price = float(price_text[0].replace(',', ''))
[Link]({'symbol': [Link], 'price': price})
class Window(QMainWindow):
def __init__(self, filename, *args, **kwargs):
super().__init__(*args, **kwargs)
[Link] 12/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
[Link] = self.read_symbols(filename)
[Link] = []
[Link]('Stock Listing')
[Link](100, 100, 400, 300)
[Link](QIcon('./assets/[Link]'))
widget = QWidget()
[Link](QGridLayout())
[Link](widget)
# set up button & progress bar
self.btn_start = QPushButton('Get Prices', clicked=self.get_prices)
self.progress_bar = QProgressBar(minimum=1, maximum=len([Link]))
# set up table widget
[Link] = QTableWidget(widget)
[Link](2)
[Link](0, 150)
[Link](1, 150)
[Link](['Symbol', 'Price'])
[Link]().addWidget([Link], 0, 0, 1, 2)
[Link]().addWidget(self.progress_bar, 1, 0)
[Link]().addWidget(self.btn_start, 1, 1)
# show the window
[Link]()
def read_symbols(self, filename):
"""
Read symbols from a file
"""
path = Path(filename)
[Link] 13/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
text = path.read_text()
return [[Link]() for symbol in [Link]('\n')]
def reset_ui(self):
self.progress_bar.setValue(1)
[Link](0)
def get_prices(self):
# reset ui
self.reset_ui()
# start worker threads
pool = [Link]()
stocks = [Stock(symbol) for symbol in [Link]]
for stock in stocks:
[Link]([Link])
[Link](stock)
def update(self, data):
# add a row to the table
row = [Link]()
[Link](row)
[Link](row, 0, QTableWidgetItem(data['symbol']))
[Link](row, 1, QTableWidgetItem(str(data['price'])))
# update the progress bar
self.progress_bar.setValue(row + 1)
# sort the list by symbols once completed
if row == len([Link]) - 1:
[Link](0, [Link])
if __name__ == '__main__':
app = QApplication([Link])
[Link] 14/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
window = Window('[Link]')
[Link]([Link]())
Comment ça marche.
Classe Signals
Nous définissons la classe qui est une sous-classe du . La classe Signals a une variable de classe
terminée qui est une instance de la classe. Signals QObject Signal
Le signal terminé contient un dictionnaire et est émis une fois que le programme a terminé
d’obtenir le cours de l’action.
class Signals(QObject):
completed = Signal(dict)
Classe d’actions
La classe hérite de la classe. Il remplace la méthode qui obtient le cours de l’action sur le site Web
de Yahoo Finance. STock QRunnable run()
Une fois terminée, la méthode émet le signal terminé avec le symbole boursier et le prix. run()
Si une erreur se produit comme si le symbole est introuvable ou si le site Web modifie la façon
dont il affiche le cours de l’action, la méthode renvoie le symbole avec le prix sous forme de chaîne
N/A. run()
class Stock(QRunnable):
BASE_URL = '[Link]
def __init__(self, symbol):
super().__init__()
[Link] = symbol
[Link] = Signals()
@Slot()
def run(self):
[Link] 15/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
stock_url = f'{self.BASE_URL}{[Link]}'
response = [Link](stock_url)
if response.status_code != 200:
[Link]({'symbol': [Link], 'price': 'N/A'}
return
tree = [Link]([Link])
price_text = [Link](
'//*[@id="quote-header-info"]/div[3]/div[1]/div[1]/fin-streamer[1]
)
if not price_text:
[Link]({'symbol': [Link], 'price': 'N/A'}
return
price = float(price_text[0].replace(',', ''))
[Link]({'symbol': [Link], 'price': price})
Notez que Yahoo Finance peut modifier sa structure. Pour que le programme fonctionne, vous
devez changer le XPath du prix du nouveau:
//*[@id="quote-header-info"]/div[3]/div[1]/div[1]/fin-streamer[1]/text()
MainWindow, classe
Tout d’abord, lisez les symboles d’un fichier et affectez-les aux variables : [Link]
[Link] = self.read_symbols(filename)
La méthode ressemble à ceci: read_symbols()
def read_symbols(self, filename):
path = Path(filename)
[Link] 16/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
text = path.read_text()
return [[Link]() for symbol in [Link]('\n')]
Le fichier texte () contient chaque symbole par ligne : [Link]
AAPL
MSFT
GOOG
AMZN
TSLA
META
NVDA
BABA
CRM
INTC
PYPL
AMD
ATVI
EA
TTD
ORCL
Deuxièmement, définissez le qui utilise le pour créer des threads de travail pour obtenir les cours
des actions: get_prices QThreadPool
def get_prices(self):
# reset ui
self.reset_ui()
# start worker threads
pool = [Link]()
stocks = [Stock(symbol) for symbol in [Link]]
for stock in stocks:
[Link] 17/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
[Link]([Link])
[Link](stock)
La méthode efface toutes les lignes de la et définit la barre de progression sur sa valeur minimale
: reset_ui() QTableWidget
def reset_ui(self):
[Link](0)
self.progress_bar.setValue(1)
Troisièmement, définissez la méthode qui sera appelée une fois chaque thread de travail terminé.
La méthode ajoute une nouvelle ligne à la table, met à jour la barre de progression et trie les
symboles une fois que tous les threads de production sont terminés : update() update()
def update(self, data):
# add a row to the table
row = [Link]()
[Link](row)
[Link](row, 0, QTableWidgetItem(data['symbol']))
[Link](row, 1, QTableWidgetItem(str(data['price'])))
# update the progress bar
self.progress_bar.setValue(row + 1)
# sort the list by symbols once completed
if row == len([Link]) - 1:
[Link](0, [Link])
Résumé
Utilisez la classe pour représenter une tâche de longue durée qui sera déchargée sur un thread
de travail. QRunnable
Utilisez la classe pour gérer automatiquement les threads de production. QThreadPool
[Link] 18/19
25/04/2023 02:01 Multithreading PyQt avec QThreadPool & QRunnable
Chaque application PyQt a un objet. Utilisez la méthode pour obtenir l’objet
global. QThreadPool globalInstance() QThreadPool
Utilisez la méthode de l’objet pour démarrer un thread de travail. start() QThreadPool
[Link] 19/19