Aller au contenu

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 :

  1. Bibliothèques tierces non réactives - Code que vous ne pouvez pas modifier et qui se bloque
  2. Opérations réseau qui bloquent - Lorsque les délais n’ont pas fonctionné
  3. Boucles infinies dans du code défectueux - Pendant le développement/débuggage
  4. 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é

  1. L’annulation coopérative devrait être votre approche par défaut
  2. La terminaison forcée est dangereuse mais parfois nécessaire
  3. Essayez toujours les méthodes gracieuses en premier
  4. Utilisez la terminaison forcée uniquement lorsque vous comprenez les risques
  5. 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 !