Solución:
Función de tapón roscado
En lugar de subclasificar threading.Thread
, se puede modificar la función para permitir la parada con una bandera.
Necesitamos un objeto, accesible a la función en ejecución, al que le ponemos la bandera para que deje de ejecutarse.
Nosotros podemos usar threading.currentThread()
objeto.
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 se basa en supuestos:
- el hilo tiene una propiedad “do_run” con valor predeterminado
True
- El proceso principal de conducción puede asignar al hilo iniciado la propiedad “do_run” a
False
.
Al ejecutar el código, obtenemos el siguiente resultado:
$ 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 defecto False
, pero el proceso externo puede “configurarlo” (para True
) y la función puede aprender sobre él usando wait(timeout)
función.
Podemos wait
con tiempo de espera cero, pero también podemos usarlo como temporizador para dormir (usado 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 su liberación. No devuelve un valor booleano. Utilizando stop_event.is_set()
funciona en su lugar.
Detener varios hilos con una pastilla
La ventaja de la píldora para matar se ve mejor si tenemos que detener varios hilos a la vez, ya que una sola píldora funcionará para todos.
los doit
no cambiará en absoluto, solo el main
maneja los hilos de manera 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()
Esto se ha preguntado antes en Stack. Vea los siguientes enlaces:
- ¿Hay alguna forma de matar un hilo en Python?
- Detener un hilo después de un cierto período de tiempo
Básicamente, solo necesita configurar el hilo con una función de parada que establece un valor centinela que el hilo comprobará. En su caso, tendrá algo en su bucle que verifique el valor centinela para ver si ha cambiado y, si lo ha hecho, el bucle puede romperse y el hilo puede morir.
Leí las otras preguntas en Stack, pero todavía estaba un poco confundido al comunicarme entre clases. Así es como lo abordé:
Utilizo una lista para guardar todos mis hilos en el __init__
método de mi clase wxFrame: self.threads = []
Como se recomienda en ¿Cómo detener un hilo en bucle en Python? Utilizo una señal en mi clase de hilo que está configurada en True
al inicializar la clase de subprocesamiento.
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