Terminaison forcée d’un thread¶
Avertissement important
La terminaison forcée doit être votre DERNIER recours ! C’est dangereux et peut causer des problèmes sérieux. Essayez toujours l’annulation coopérative en premier.
Méthodes pour la terminaison forcée d’un thread¶
1. terminate() - L’option nucléaire¶
if self.worker_thread.isRunning():
self.worker_thread.terminate() # Tuer le thread de force
self.worker_thread.wait() # Attendre le nettoyage
Ce que cela fait :
- Arrête immédiatement l’exécution du thread
- Aucun code de nettoyage ne s’exécute
- Le thread s’arrête où qu’il soit dans le code
Dangers :
- Corruption des données - Les fichiers peuvent être laissés à moitié écrits
- Fuites de ressources - Fichiers ouverts, connexions réseau laissées en attente
- Interblocages - Ressources verrouillées restent verrouillées pour toujours
- Corruption mémoire - Variables laissées dans un état incohérent
2. quit() + wait() - Un peu moins agressif¶
if self.worker_thread.isRunning():
self.worker_thread.quit() # Demander au thread de quitter
self.worker_thread.wait(5000) # Attendre jusqu'à 5 secondes
Ce que cela fait :
- Envoie un signal de sortie à la boucle d’événements du thread
- Le thread peut potentiellement faire un peu de nettoyage
- Plus gracieux que
terminate()
Toujours dangereux parce que :
- Le thread pourrait ignorer le signal de sortie
- Si le thread est bloqué dans une boucle, quit ne fonctionnera pas
- Vous pourriez encore avoir besoin d’utiliser
terminate()ensuite
3. Motif avec délai + force (Approche recommandée)¶
def force_stop_thread(self):
"""Essayer l'arrêt coopératif en premier, puis forcer si nécessaire"""
if not self.worker_thread or not self.worker_thread.isRunning():
return
# Étape 1 : Essayer l'annulation coopérative
self.worker_thread.cancel() # Définir le drapeau d'annulation
# Étape 2 : Attendre brièvement pour l'arrêt coopératif
if self.worker_thread.wait(2000): # Attendre 2 secondes
print("Thread arrêté de manière coopérative")
return
# Étape 3 : Essayer le signal quit
self.worker_thread.quit()
if self.worker_thread.wait(1000): # Attendre 1 seconde de plus
print("Thread arrêté après le signal quit")
return
# Étape 4 : Dernier recours - terminer
print("⚠️ Terminaison forcée du thread non réactif")
self.worker_thread.terminate()
self.worker_thread.wait() # Attendre la fin de la terminaison
Quand la terminaison forcée pourrait être nécessaire¶
Cas d’utilisation légitimes :¶
- Bibliothèques tierces non réactives - Code que vous ne pouvez pas modifier et qui se bloque
- Opérations réseau qui bloquent - Lorsque les délais n’ont pas fonctionné
- Boucles infinies dans du code défectueux - Pendant le développement/débuggage
- Fermeture de l’application - L’utilisateur ferme l’application alors que des threads s’exécutent
Exemples de scénarios :¶
# Opération réseau bloquée
try:
response = requests.get(url, timeout=30) # Devrait expirer...
except:
pass # ...mais parfois non !
# Boucle infinie (code défectueux)
while True: # Oublié de mettre à jour la condition de boucle
process_data()
# Bibliothèque non réactive
some_library.blocking_operation() # Aucune façon d'annuler cela
Meilleure alternative à la terminaison forcée¶
1. Concevoir pour l’annulation dès le départ¶
class WorkerBienComporté(QThread):
def run(self):
for i in range(1000):
if self.is_cancelled: # Vérifier régulièrement !
return
# Faire le travail par petites quantités
self.do_small_amount_of_work()
2. Utiliser des délais dans les opérations réseau¶
import requests
def safe_network_call(self):
try:
response = requests.get(url, timeout=10) # Toujours utiliser des délais !
return response
except requests.Timeout:
self.log_message.emit("La requête réseau a expiré")
return None
3. Traiter les grandes tâches par morceaux¶
def process_large_file(self):
with open(file_path, 'r') as file:
while True:
if self.is_cancelled: # Vérifier entre les morceaux
return
chunk = file.read(1024) # Traiter en petites quantités
if not chunk:
break
self.process_chunk(chunk)
Points importants¶
1. La prévention vaut mieux que le remède¶
- Concevez toujours les threads pour qu’ils soient annulables
- Utilisez des délais pour toutes les opérations réseau
- Traitez les grandes tâches en petits morceaux interruptibles
2. Le contrat d’annulation¶
# Modèle de thread worker bon
class BonWorker(QThread):
def __init__(self):
super().__init__()
self.is_cancelled = False
def cancel(self):
self.is_cancelled = True
def run(self):
for item in large_dataset:
if self.is_cancelled: # Respecter le contrat !
return
self.process_item(item)
3. Quand la terminaison forcée est acceptable¶
- Pendant la fermeture de l’application (l’utilisateur quitte de toute façon)
- Pour déboguer du code non réactif pendant le développement
- En dernier recours absolu lorsque l’intégrité des données n’est pas critique
4. Quand la terminaison forcée n’est JAMAIS acceptable¶
- Lors de la gestion de données financières
- Lors de l’écriture dans des bases de données
- Lors de la gestion de fichiers utilisateur
- Dans les applications en production (sauf si spécifiquement conçu pour cela)
Exemple du monde réel : Fermeture de l’application¶
def closeEvent(self, event):
"""Gérer la fermeture de l'application"""
if self.worker_thread and self.worker_thread.isRunning():
# Essayer d'abord une fermeture coopérative
self.worker_thread.cancel()
if not self.worker_thread.wait(3000): # Attendre 3 secondes
# Terminer de force à la fermeture de l'application - cela est acceptable
self.worker_thread.terminate()
self.worker_thread.wait()
event.accept()
Résumé¶
- L’annulation coopérative devrait être votre approche par défaut
- La terminaison forcée est dangereuse mais parfois nécessaire
- Essayez toujours les méthodes gracieuses en premier
- Utilisez la terminaison forcée uniquement lorsque vous comprenez les risques
- Concevez vos threads pour qu’ils soient bien comportés dès le départ
Rappelez-vous : Une application bien conçue devrait rarement avoir besoin de terminaison forcée !