Jak zatrzymać pętlę wątku w Pythonie?
Jaki jest właściwy sposób, aby powiedzieć pętli wątku, aby przestać pętli?
Mam dość prosty program, który ping podanego hosta w oddzielnej klasie threading.Thread
. W tej klasie śpi 60 sekund, uruchamia się ponownie, dopóki aplikacja nie zostanie zamknięta.
Chciałbym zaimplementować przycisk "Stop" w moim wx.Frame
, aby poprosić wątek pętli o zatrzymanie. Nie musi od razu kończyć wątku, może po prostu przestać zapętlać się, gdy się obudzi.
Oto moja threading
klasa (uwaga: nie zaimplementowano jeszcze pętlę, ale prawdopodobnie będzie ona podlegała metodzie run w 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)
A w mojej ramce wxPyhton mam taką funkcję wywoływaną z przycisku Start:
def CheckAsset(self, asset):
self.count += 1
thread = PingAssets(self.count, asset, self)
self.threads.append(thread)
thread.start()
4 answers
O to pytano już wcześniej na stacku. Zobacz następujące linki:
Zasadniczo wystarczy skonfigurować wątek za pomocą funkcji stop, która ustawia wartość sentinel, którą wątek będzie sprawdzał. W Twoim przypadku będziesz miał coś w swojej pętli sprawdź wartość sentinel, aby sprawdzić, czy się zmieniło, a jeśli tak, pętla może się złamać i wątek może umrzeć.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:03
Threaded stoppable function
Zamiast podklasowania threading.Thread
, można zmodyfikować funkcję, aby umożliwić
zatrzymuję się przy fladze.
Potrzebujemy obiektu, dostępnego dla uruchomionej funkcji, dla którego ustawiamy flagę, aby przestała działać.
Możemy użyć threading.currentThread()
obiektu.
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()
Sztuczka polega na tym, że uruchomiony wątek może mieć dołączone dodatkowe właściwości. Rozwiązanie buduje na podstawie założeń:
- wątek ma właściwość "do_run" z wartością domyślną
True
- proces macierzysty może przypisać do rozpoczętego wątku właściwość "do_run" do
False
.
Uruchamiając kod, otrzymujemy następujące wyjście:
$ python stopthread.py
working on task
working on task
working on task
working on task
working on task
Stopping as you wish.
Pill to kill-using Event
Inną alternatywą jest użycie threading.Event
jako argumentu funkcji. Jest przez
domyślna False
, ale zewnętrzny proces może ją "ustawić" (na True
) i funkcja może
dowiedz się o tym za pomocą funkcji wait(timeout)
.
Możemy wait
z zerowym timeoutem, ale możemy również użyć go jako timera uśpienia (użyte poniżej).
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: próbowałem tego w Pythonie 3.6. stop_event.wait()
blokuje Zdarzenie (a więc pętlę while) do momentu zwolnienia. Nie zwraca wartości logicznej. Zamiast tego działa stop_event.is_set()
.
Zatrzymanie wielu wątków za pomocą jednej pigułki
Przewaga pigułki do zabijania jest lepiej widoczna, jeśli musimy zatrzymać wiele wątków na raz, jak jedna tabletka będzie działać dla wszystkich.
doit
w ogóle się nie zmieni, tylko main
obsługuje wątki nieco inaczej.
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/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-23 13:36:52
Czytałem inne pytania na Stack, ale nadal byłem trochę zdezorientowany w komunikowaniu się między klasami. Oto jak podszedłem:
Używam listy do przechowywania wszystkich wątków w metodzie __init__
mojej klasy wxFrame: self.threads = []
Zgodnie z zaleceniami w Jak zatrzymać pętlę wątku w Pythonie? używam sygnału w mojej klasie wątku, który jest ustawiony na True
podczas inicjalizacji klasy wątku.
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()
I mogę zatrzymać te wątki przez iterację nad moimi wątkami:
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/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:11
Miałem inne podejście. Podklasowałem klasę wątku, a w konstruktorze utworzyłem obiekt Event. Następnie napisałem własną metodę join (), która najpierw ustawia to zdarzenie, a następnie wywołuje samą wersję rodzica.
Oto moja klasa, której używam do komunikacji z portami szeregowymi w aplikacji 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()
Kolejka służy do wysyłania wiadomości do portu, a główna pętla odbiera odpowiedzi z powrotem. Nie użyłem żadnego seryjnego.metoda readline (), ze względu na inny znak końca linii, i uznałem, że używanie klas io jest zbyt kłopotliwe.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-18 12:11:53