threads - ¿Cómo detener un hilo de bucle en Python?
thread start() python (4)
Función stoppable roscada
En lugar de crear threading.Thread
de threading.Thread
. threading.Thread
, se puede modificar la función para permitir detener una marca.
Necesitamos un objeto, accesible a la función en ejecución, a la que configuramos 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 se basa en suposiciones:
- el hilo tiene una propiedad "do_run" con el valor predeterminado
True
- el proceso principal de conducción puede asignar a la propiedad "do_run" iniciada en
False
.
Ejecutando 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 la función. Es False
por defecto, pero el proceso externo puede "configurarlo" (en True
) y la función puede aprender sobre él usando la función de wait(timeout)
.
Podemos wait
sin tiempo límite, pero también podemos usarlo como el temporizador para dormir (se usa 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()
Edit: probé esto en Python 3.6. stop_event.wait()
bloquea el evento (y así el bucle while) hasta su liberación. No devuelve un valor booleano. Usar 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 píldora funcionará para todos.
El 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()
¿Cuál es la forma correcta de decirle a un subproceso de bucle que detenga el bucle?
Tengo un programa bastante simple que hace ping a un host específico en una clase threading.Thread
separada. En esta clase duerme 60 segundos, se ejecuta de nuevo hasta que la aplicación se cierra.
Me gustaría implementar un botón ''Detener'' en mi wx.Frame
para pedirle al subproceso en bucle que se detenga. No necesita terminar el hilo de inmediato, solo puede detener el bucle una vez que se despierta.
Aquí está mi clase de threading
(nota: no he implementado bucles todavía, pero es probable que caiga bajo el método de ejecución 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()
Esto ha sido preguntado antes en Stack. Vea los siguientes enlaces:
Básicamente, solo necesita configurar el subproceso con una función de parada que establece un valor de centinela que el hilo verificará. En su caso, tendrá algo en su bucle que verifica el valor del centinela para ver si ha cambiado y si lo ha hecho, el bucle se puede romper 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é:
Uso una lista para guardar todos mis hilos en el método __init__
de mi clase self.threads = []
: self.threads = []
Como se recomienda en ¿Cómo detener un hilo de bucle en Python? Utilizo una señal en mi clase de hilo que se establece en True
al inicializar la clase de hilo.
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 al iterar sobre mis hilos:
def OnStop(self, e):
for t in self.threads:
t.signal = False
Tuve un enfoque diferente. Subclasificé una clase Thread y en el constructor he creado un objeto de evento. Luego escribí el método join () personalizado, que primero establece este evento y luego llama a la versión de uno de los padres.
Aquí está mi clase, estoy usando para la comunicación del 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 utiliza para enviar mensajes al puerto y el bucle principal recupera las respuestas. No he usado ningún método serial.readline () debido a las diferentes características de la línea final, y he encontrado que el uso de las clases io es demasiado complicado.