Завершение не-демонических потоков при завершении интерактивного сеанса Python - PullRequest
0 голосов
/ 23 мая 2018

Пожалуйста, рассмотрите код ниже:

#! /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()

После сохранения этого файла как 'test.py' и запуска 'python3 -i test.py' я получаю интерактивный сеанс, где поток регулярно печатает"Нить жива!"message.

Когда я нажимаю control-D или запускаю exit (), процесс python3 не завершается, потому что поток все еще работает.Я хочу это исправить.

Тем не менее, Я не хочу делать поток потоком демона - я хочу иметь возможность правильно завершить / присоединиться к потоку, потому что в реальномя решаю дело, хочу красиво разорвать сетевое соединение.

Есть ли способ сделать это?Например, есть ли доступный хук, который выполняется после завершения цикла REPL, непосредственно перед выходом из основного потока?

Ответы [ 3 ]

0 голосов
/ 24 мая 2018

Хорошо, я нашел способ сделать это сам.

Глядя на исходный код Python, выясняется, что при завершении работы Python пытается присоединиться () ко всем недемоническим потокам до завершения завершения работыпроцесс.Какой тип имеет смысл, за исключением того, что это было бы гораздо более полезным, если бы существовал документированный способ сигнализации этих потоков.Чего нет.

Однако, позволяет повторно реализовать метод join () своего собственного класса, производного от Thread, который может служить способом уведомленияподзадача о том, что он должен сам отключиться:

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()

Однако, этот способ решения проблемы в значительной степени является хакерским, и он игнорирует следующий отрывок из документации модуля 'threading' (https://docs.python.org/3/library/threading.html):

Класс Thread представляет действие, которое запускается в отдельном потоке управления. Существует два способа указать действие: путем передачи вызываемого объекта в конструктор или переопределением выполнения () в подклассе. Никакие другие методы (кроме конструктора) не должны быть переопределены в подклассе. Другими словами, только переопределите методы __init __ () и run () этого класса .

Тем не менее, он прекрасно работает.

0 голосов
/ 24 мая 2018

Я думаю, что вы можете сделать это, зарегистрировав функцию очистки, используя atexit, и установите ваш поток в качестве демона, например:

#! /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()
0 голосов
/ 23 мая 2018

Я не уверен, что это приемлемо, но единственный способ увидеть вашу проблему - это запустить интерактивную консоль из сценария, а не с флагом -i.После этого вы сможете продолжить работу с программой для завершения после завершения интерактивного сеанса:

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()

Это будет выполняться без флага -i, только как

python3 test.py

Это все равно даст вам интерактивную консоль, как с python -i, но теперь сценарий выполняет exit_gracefully() после выхода из интерактивной оболочки.

...