[英]How to stop a looping thread in Python?
告訴循環線程停止循環的正確方法是什么?
我有一個相當簡單的程序,可以在單獨的threading.Thread
類中 ping 指定的主機。 在這個類中它會休眠 60 秒,然后再次運行直到應用程序退出。
我想在我的wx.Frame
實現一個“停止”按鈕來要求循環線程停止。 它不需要立即結束線程,它可以在喚醒后停止循環。
這是我的threading
類(注意:我還沒有實現循環,但它可能屬於 PingAssets 中的 run 方法)
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)
在我的 wxPyhton 框架中,我從“開始”按鈕調用了這個函數:
def CheckAsset(self, asset):
self.count += 1
thread = PingAssets(self.count, asset, self)
self.threads.append(thread)
thread.start()
除了繼承threading.Thread
,還可以修改該函數以允許通過標志停止。
我們需要一個可以訪問運行函數的對象,我們將標志設置為停止運行。
我們可以使用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
if __name__ == "__main__":
main()
訣竅是,正在運行的線程可以附加其他屬性。 該解決方案基於以下假設:
True
False
。運行代碼,我們得到以下輸出:
$ python stopthread.py
working on task
working on task
working on task
working on task
working on task
Stopping as you wish.
其他替代方法是使用threading.Event
作為函數參數。 它默認為False
,但外部進程可以“設置它”(為True
)並且函數可以使用wait(timeout)
函數了解它。
我們可以在零超時的情況下wait
,但我們也可以將其用作睡眠定時器(下面使用)。
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()
編輯:我在 Python 3.6 中嘗試過這個。 stop_event.wait()
阻止事件(以及 while 循環)直到釋放。 它不返回布爾值。 使用stop_event.is_set()
代替。
葯丸殺死的優勢更明顯,如果我們必須一次停止多個線程,因為一顆葯丸對所有線程都有效。
doit
根本不會改變,只有main
處理線程有點不同。
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()
之前在 Stack 上已經問過這個問題。 請參閱以下鏈接:
基本上,您只需要使用停止函數設置線程,該函數設置線程將檢查的標記值。 在您的情況下,您將讓循環中的某些內容檢查哨兵值以查看它是否已更改,如果已更改,則循環可能會中斷並且線程可能會死亡。
我閱讀了 Stack 上的其他問題,但我對跨類交流仍然有些困惑。 這是我如何處理它:
我使用一個列表將所有線程保存在 wxFrame 類的__init__
方法中: self.threads = []
正如如何在 Python 中停止循環線程中所推薦的那樣? 我在線程類中使用了一個信號,該信號在初始化線程類時設置為True
。
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()
我可以通過迭代我的線程來停止這些線程:
def OnStop(self, e):
for t in self.threads:
t.signal = False
我有不同的方法。 我對 Thread 類進行了子類化,並在構造函數中創建了一個 Event 對象。 然后我編寫了自定義 join() 方法,該方法首先設置此事件,然后調用自身的父版本。
這是我的課程,我在 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()
隊列用於向端口發送消息,主循環接收響應。 我沒有使用 serial.readline() 方法,因為不同的結束行字符,我發現 io 類的使用太麻煩了。
取決於您在該線程中運行的內容。 如果那是您的代碼,那么您可以實現停止條件(請參閱其他答案)。
然而,如果你想要運行別人的代碼,那么你應該fork並啟動一個進程。 像這樣:
import multiprocessing
proc = multiprocessing.Process(target=your_proc_function, args=())
proc.start()
現在,每當您想停止該進程時,請向其發送一個 SIGTERM,如下所示:
proc.terminate()
proc.join()
而且它並不慢:幾分之一秒。 享受 :)
我的解決辦法是:
import threading, time
def a():
t = threading.currentThread()
while getattr(t, "do_run", True):
print('Do something')
time.sleep(1)
def getThreadByName(name):
threads = threading.enumerate() #Threads list
for thread in threads:
if thread.name == name:
return thread
threading.Thread(target=a, name='228').start() #Init thread
t = getThreadByName('228') #Get thread by name
time.sleep(5)
t.do_run = False #Signal to stop thread
t.join()
我發現有一個派生自threading.Thread
的類來封裝我的線程功能很有用。 您只需在此類中的run()
覆蓋版本中提供您自己的主循環。 調用start()
安排在單獨的線程中調用對象的run()
方法。
在主循環內,定期檢查是否設置了threading.Event
。 這樣的事件是線程安全的。
在這個類中,您有自己的join()
方法,該方法在調用基類的join()
方法之前設置停止事件對象。 可以選擇將時間值傳遞給基類的join()
方法,以確保您的線程在短時間內終止。
import threading
import time
class MyThread(threading.Thread):
def __init__(self, sleep_time=0.1):
self._stop_event = threading.Event()
self._sleep_time = sleep_time
"""call base class constructor"""
super().__init__()
def run(self):
"""main control loop"""
while not self._stop_event.isSet():
#do work
print("hi")
self._stop_event.wait(self._sleep_time)
def join(self, timeout=None):
"""set stop event and join within a given time period"""
self._stop_event.set()
super().join(timeout)
if __name__ == "__main__":
t = MyThread()
t.start()
time.sleep(5)
t.join(1) #wait 1s max
在檢查threading.Event
之前,在主循環內有一個小的睡眠。事件比連續循環占用更少的 CPU。 您可以有一個默認的睡眠時間(例如 0.1 秒),但您也可以在構造函數中傳遞該值。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.