Cómo detener un hilo de bucle en Python?
¿Cuál es la forma correcta de decirle a un hilo de bucle que deje de hacerlo?
Tengo un programa bastante simple que hace ping a un host especificado en una clase threading.Thread
separada. En esta clase duerme 60 segundos, se ejecuta de nuevo hasta que se cierra la aplicación.
Me gustaría implementar un botón 'Stop' en mi wx.Frame
para pedir que el hilo de bucle se detenga. No es necesario terminar el hilo de inmediato, solo puede dejar de girar una vez que se despierte.
Aquí está mi clase threading
(nota: No he implementado looping todavía, pero probablemente caería bajo el método run en PingAssets)
class PingAssets(threading.Thread):
def __init__(self, threadNum, asset, window):
threading.Thread.__init__(self)
self.threadNum = threadNum
self.window = window
self.asset = asset
def run(self):
config = controller.getConfig()
fmt = config['timefmt']
start_time = datetime.now().strftime(fmt)
try:
if onlinecheck.check_status(self.asset):
status = "online"
else:
status = "offline"
except socket.gaierror:
status = "an invalid asset tag."
msg =("{}: {} is {}. \n".format(start_time, self.asset, status))
wx.CallAfter(self.window.Logger, msg)
Y en mi marco wxPyhton tengo esta función llamada desde un botón de inicio:
def CheckAsset(self, asset):
self.count += 1
thread = PingAssets(self.count, asset, self)
self.threads.append(thread)
thread.start()
4 answers
Esto se ha preguntado antes en la pila. Vea los siguientes enlaces:
Básicamente solo necesita configurar el subproceso con una función stop que establezca un valor sentinel que el subproceso verificará. En tu caso, tendrás algo en tu bucle comprueba el valor de sentinel para ver si ha cambiado y si lo ha hecho, el bucle puede romperse y el hilo puede morir.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:03
Función tapable roscada
En lugar de subclase threading.Thread
, se puede modificar la función para permitir
pasando por una bandera.
Necesitamos un objeto, accesible para la función en ejecución, en el que establecemos la bandera para que deje de ejecutarse.
Podemos usar el objeto threading.currentThread()
.
import threading
import time
def doit(arg):
t = threading.currentThread()
while getattr(t, "do_run", True):
print ("working on %s" % arg)
time.sleep(1)
print("Stopping as you wish.")
def main():
t = threading.Thread(target=doit, args=("task",))
t.start()
time.sleep(5)
t.do_run = False
t.join()
if __name__ == "__main__":
main()
El truco es que el hilo en ejecución puede tener propiedades adicionales adjuntas. La solución construye en supuestos:
- el hilo tiene una propiedad "do_run" con valor predeterminado
True
- el proceso padre de conducción puede asignar al hilo iniciado la propiedad "do_run" a
False
.
Ejecutando el código, obtenemos la siguiente salida:
$ python stopthread.py
working on task
working on task
working on task
working on task
working on task
Stopping as you wish.
Píldora para matar - usando Evento
Otra alternativa es usar threading.Event
como argumento de función. Es por
por defecto False
, pero el proceso externo puede "establecerlo" (a True
) y la función puede
aprende sobre ello usando la función wait(timeout)
.
Podemos wait
con cero tiempo de espera, pero también podemos usarlo como el temporizador para dormir (utilizado a continuación).
def doit(stop_event, arg):
while not stop_event.wait(1):
print ("working on %s" % arg)
print("Stopping as you wish.")
def main():
pill2kill = threading.Event()
t = threading.Thread(target=doit, args=(pill2kill, "task"))
t.start()
time.sleep(5)
pill2kill.set()
t.join()
Editar: Probé esto en Python 3.6. stop_event.wait()
bloquea el evento (y por lo tanto el bucle while) hasta el lanzamiento. No devuelve un valor booleano. Usar stop_event.is_set()
funciona en su lugar.
Detener múltiples hilos con una píldora
La ventaja de la píldora para matar se ve mejor, si tenemos que detener múltiples hilos a la vez, como una píldora funcionará para todos.
El doit
no cambiará en absoluto, solo el main
maneja los hilos de forma un poco diferente.
def main():
pill2kill = threading.Event()
tasks = ["task ONE", "task TWO", "task THREE"]
def thread_gen(pill2kill, tasks):
for task in tasks:
t = threading.Thread(target=doit, args=(pill2kill, task))
yield t
threads = list(thread_gen(pill2kill, tasks))
for thread in threads:
thread.start()
time.sleep(5)
pill2kill.set()
for thread in threads:
thread.join()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-23 13:36:52
Leí las otras preguntas en Stack, pero todavía estaba un poco confundido sobre la comunicación entre clases. Así es como lo abordé:
Uso una lista para contener todos mis hilos en el método __init__
de mi clase wxFrame: self.threads = []
Como se recomienda en ¿Cómo detener un hilo de bucle en Python? Uso una señal en mi clase thread que se establece en True
al inicializar la clase threading.
class PingAssets(threading.Thread):
def __init__(self, threadNum, asset, window):
threading.Thread.__init__(self)
self.threadNum = threadNum
self.window = window
self.asset = asset
self.signal = True
def run(self):
while self.signal:
do_stuff()
sleep()
Y puedo detener estos hilos iterando sobre mis hilos:
def OnStop(self, e):
for t in self.threads:
t.signal = False
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:11
Yo tenía un enfoque diferente. He subclasificado una clase Thread y en el constructor he creado un objeto Event. Luego he escrito el método custom join (), que primero establece este evento y luego llama a la versión de sí mismo de un padre.
Aquí está mi clase, que estoy usando para la comunicación de puerto serie en la aplicación wxPython:
import wx, threading, serial, Events, Queue
class PumpThread(threading.Thread):
def __init__ (self, port, queue, parent):
super(PumpThread, self).__init__()
self.port = port
self.queue = queue
self.parent = parent
self.serial = serial.Serial()
self.serial.port = self.port
self.serial.timeout = 0.5
self.serial.baudrate = 9600
self.serial.parity = 'N'
self.stopRequest = threading.Event()
def run (self):
try:
self.serial.open()
except Exception, ex:
print ("[ERROR]\tUnable to open port {}".format(self.port))
print ("[ERROR]\t{}\n\n{}".format(ex.message, ex.traceback))
self.stopRequest.set()
else:
print ("[INFO]\tListening port {}".format(self.port))
self.serial.write("FLOW?\r")
while not self.stopRequest.isSet():
msg = ''
if not self.queue.empty():
try:
command = self.queue.get()
self.serial.write(command)
except Queue.Empty:
continue
while self.serial.inWaiting():
char = self.serial.read(1)
if '\r' in char and len(msg) > 1:
char = ''
#~ print('[DATA]\t{}'.format(msg))
event = Events.PumpDataEvent(Events.SERIALRX, wx.ID_ANY, msg)
wx.PostEvent(self.parent, event)
msg = ''
break
msg += char
self.serial.close()
def join (self, timeout=None):
self.stopRequest.set()
super(PumpThread, self).join(timeout)
def SetPort (self, serial):
self.serial = serial
def Write (self, msg):
if self.serial.is_open:
self.queue.put(msg)
else:
print("[ERROR]\tPort {} is not open!".format(self.port))
def Stop(self):
if self.isAlive():
self.join()
La Cola se usa para enviar mensajes al puerto y el bucle principal recupera las respuestas. No he usado ninguna serie.método readline (), debido a diferentes caracteres de línea final, y he encontrado que el uso de las clases io es demasiado alboroto.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-18 12:11:53