Veuillez considérer le code ci-dessous:

#! /usr/bin/env python3

import threading
import time

class MyThread(threading.Thread):
    def __init__(self):
        super().__init__()
        self._quit_flag = False
    def run(self):
        while not self._quit_flag:
            print("Thread is alive!")
            time.sleep(0.500)
    def request_quit(self):
        self._quit_flag = True

mt = MyThread()
mt.start()

Après avoir enregistré ceci sous 'test.py' et exécuté 'python3 -i test.py', j'obtiens une session interactive, où le thread imprime régulièrement le "Thread is alive!" message.

Lorsque j'appuie sur control-D ou que j'exécute exit (), le processus python3 ne se termine pas car le thread est toujours en cours d'exécution. Je veux arranger ça.

Cependant, je ne veux pas faire du thread un thread démon - je veux pouvoir terminer / rejoindre le thread correctement, parce que dans le cas réel que je résous, je veux mettre fin à une connexion réseau correctement.

Y a-t-il un moyen de faire cela? Par exemple, existe-t-il un hook disponible qui est exécuté après la fin de la boucle REPL, juste avant la sortie du thread principal?

2
reddish 23 mai 2018 à 13:31

3 réponses

Meilleure réponse

D'accord, j'ai trouvé un moyen de le faire moi-même.

En regardant le code source Python, il s'avère qu'à l'arrêt, Python tente de joindre () tous les threads non démoniaques avant de terminer l'arrêt du processus. Quelle sorte de sens, sauf que cela serait beaucoup plus utile s'il y avait un moyen documenté de signaler ces threads. Ce qui n'est pas le cas.

Cependant, il est possible de réimplémenter la méthode join () de sa propre classe dérivée de Thread, qui peut servir de moyen de notifier au sous-thread qu'il est censé s'arrêter:

class MyThread(threading.Thread):
    def __init__(self):
        super().__init__()
        self._quit_flag = False
    def __del__(self):
        print("Bye bye!")
    def run(self):
        while not self._quit_flag:
            print("Thread is alive!",  threading.active_count(), threading.main_thread().is_alive())
            for t in threading.enumerate():
                print(t.is_alive())
            time.sleep(0.500)
    def request_quit(self):
        self._quit_flag = True

    def join(self):
        self.request_quit()
        super().join()

Cependant, cette façon de résoudre le problème est à peu près un hack, et elle ignore le passage suivant des documents du module 'threading' (https://docs.python.org/3/library/threading.html):

La classe Thread représente une activité qui est exécutée dans un environnement distinct fil de contrôle. Il existe deux façons de spécifier l'activité: par en passant un objet appelable au constructeur, ou en remplaçant le méthode run () dans une sous-classe. Aucune autre méthode (à l'exception de constructeur) doit être remplacé dans une sous-classe. En d'autres termes, seulement remplacer les méthodes __init __ () et run () de cette classe .

Cependant, cela fonctionne à merveille.

2
reddish 24 mai 2018 à 05:09

Je ne sais pas si cela serait acceptable, mais la seule façon dont je peux voir autour de votre problème est de démarrer la console interactive à partir de votre script plutôt qu'avec l'indicateur -i. Ensuite, vous pourrez continuer avec votre programme pour faire la sortie, après avoir terminé votre session interactive:

import threading
import time
from code import InteractiveConsole

def exit_gracefully():
    print("exiting gracefully now")
    global mt
    mt.request_quit()
    mt.join()


class MyThread(threading.Thread):
    def __init__(self):
        super().__init__()
        self._quit_flag = False
    def run(self):
        while not self._quit_flag:
            print("Thread is alive!")
            time.sleep(0.500)
    def request_quit(self):
        self._quit_flag = True

mt = MyThread()
mt.start()
InteractiveConsole(locals=locals()).interact()
exit_gracefully()

Vous l'exécuteriez sans l'indicateur -i, uniquement comme

python3 test.py

Cela vous donnerait toujours la console interactive comme avec python -i, mais maintenant le script s'exécute exit_gracefully() après la fermeture du shell interactif.

1
Hannu 23 mai 2018 à 11:35

Je pense que la façon dont vous pouvez le faire est d'enregistrer une fonction de nettoyage en utilisant atexit et de définir votre thread comme un démon, par exemple:

#! /usr/bin/env python3

import threading
import time
import atexit

class MyThread(threading.Thread):
    def __init__(self):
        super().__init__()

        # this ensures that atexit gets called.
        self.daemon = True
        self._quit_flag = False

    def run(self):
        while not self._quit_flag:
            print("Thread is alive!")
            time.sleep(0.500)
        print("cleanup can also go here")

    def request_quit(self):
        print("cleanup can go here")
        self._quit_flag = True


mt = MyThread()
def cleanup():
    mt.request_quit()
    mt.join()
    print("even more cleanup here, so many options!")

atexit.register(cleanup)
mt.start()
1
Bi Rico 24 mai 2018 à 05:50