Quelle est la bonne façon de dire à un thread en boucle d'arrêter de boucler?
J'ai un programme assez simple qui envoie une commande ping à un hôte spécifié dans une classe séparée threading.Thread
. Dans cette classe, il dort 60 secondes, il s'exécute de nouveau jusqu'à la fermeture de l'application.
Je voudrais implémenter un bouton 'Stop' dans mon wx.Frame
pour demander au thread en boucle de s'arrêter. Il n'est pas nécessaire de terminer le fil immédiatement, il peut simplement arrêter de boucler une fois qu'il est réveillé.
Voici ma classe threading
(note: je n'ai pas encore implémenté la mise en boucle, mais cela tomberait probablement sous la méthode run dans 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)
Et dans mon cadre wxPyhton, cette fonction est appelée à partir d'un bouton Démarrer:
def CheckAsset(self, asset):
self.count += 1
thread = PingAssets(self.count, asset, self)
self.threads.append(thread)
thread.start()
Au lieu de sous-classer threading.Thread
, on peut modifier la fonction pour permettre l’arrêt par un drapeau.
Nous avons besoin d'un objet, accessible à la fonction en cours d'exécution, sur lequel nous définissons l'indicateur pour qu'il cesse de courir.
Nous pouvons utiliser l'objet 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 __== "__main__":
main()
L'astuce est que le thread en cours d'exécution peut avoir des propriétés supplémentaires attachées. La solution repose sur des hypothèses:
True
False
.En exécutant le code, nous obtenons la sortie suivante:
$ python stopthread.py
working on task
working on task
working on task
working on task
working on task
Stopping as you wish.
Une autre alternative consiste à utiliser threading.Event
comme argument de fonction. Il s'agit par défaut de False
, mais un processus externe peut le "régler" (sur True
) et la fonction peut en apprendre davantage à l'aide de la fonction wait(timeout)
.
Nous pouvons wait
avec zéro délai d'attente, mais nous pouvons également l'utiliser comme minuteur en veille (utilisé ci-dessous).
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: J'ai essayé ceci dans Python 3.6. stop_event.wait()
bloque l'événement (et donc la boucle while) jusqu'à sa libération. Il ne renvoie pas de valeur booléenne. Utiliser stop_event.is_set()
fonctionne à la place.
On voit mieux l'avantage de la pilule à tuer, s'il faut arrêter plusieurs threads à la fois, car une pilule fonctionnera pour tous.
La doit
ne changera pas du tout, seule la main
gérera les threads un peu différemment.
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()
Cela a été demandé auparavant sur Stack. Voir les liens suivants:
En gros, il vous suffit de configurer le thread avec une fonction stop qui définit une valeur sentinel que le thread va vérifier. Dans votre cas, vous devez demander à quelque chose dans votre boucle de vérifier la valeur de sentinelle pour voir si elle a changé et si c'est le cas, la boucle peut se rompre et le fil peut mourir.
J'ai lu les autres questions sur Stack mais j'étais toujours un peu confus de communiquer entre classes. Voici comment je l'ai abordé:
J'utilise une liste pour contenir tous mes threads dans la méthode __init__
de ma classe wxFrame: self.threads = []
Comme recommandé dans Comment arrêter un thread en boucle dans Python? J'utilise un signal dans ma classe de thread qui est défini sur True
lors de l'initialisation de la classe de thread.
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()
et je peux arrêter ces discussions en parcourant mes discussions:
def OnStop(self, e):
for t in self.threads:
t.signal = False
J'ai eu une approche différente. J'ai sous-classé une classe Thread et dans le constructeur, j'ai créé un objet Event. Ensuite, j'ai écrit la méthode join () personnalisée, qui définit d'abord cet événement, puis appelle sa version parent.
Voici ma classe que j'utilise pour la communication par port série dans l'application 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 file d'attente est utilisée pour envoyer des messages au port et la boucle principale récupère les réponses. Je n'ai pas utilisé de méthode serial.readline (), en raison de caractères différents en fin de ligne, et j'ai constaté que l'utilisation des classes io était trop compliquée.