告诉循环线程停止循环的正确方法是什么?
我有一个相当简单的程序,它在一个单独的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()
答案 0 :(得分:70)
可以修改要允许的函数,而不是子类化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
t.join()
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()
答案 1 :(得分:24)
之前已经在Stack上询问过这个问题。请参阅以下链接:
基本上你只需要设置一个带有stop函数的线程,该函数设置线程将检查的sentinel值。在你的情况下,你将循环中的东西检查sentinel值以查看它是否已更改,如果有,则循环可能会中断并且线程可能会死亡。
答案 2 :(得分:11)
我在Stack上阅读了其他问题,但我仍然对跨类通信感到困惑。以下是我如何处理它:
我使用一个列表来保存我的wxFrame类的__init__
方法中的所有主题:self.threads = []
根据How to stop a looping thread in 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
答案 3 :(得分:0)
我采用了不同的方法。我已经对一个Thread类进行了细分,并且在构造函数中我创建了一个Event对象。然后我编写了自定义join()方法,该方法首先设置此事件,然后调用父本身的版本。
这是我的课程,我在wxPython app中用于串口通信:
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()
Queue用于向端口发送消息,主循环返回响应。我没有使用serial.readline()方法,因为不同的end-line char,我发现io类的用法太过分了。
答案 4 :(得分:0)
取决于您在该线程中运行的内容。 如果那是您的代码,则可以实现停止条件(请参阅其他答案)。
但是,如果要运行其他人的代码,则应该分叉并启动一个过程。像这样:
import multiprocessing
proc = multiprocessing.Process(target=your_proc_function, args=())
proc.start()
现在,只要您想停止该过程,就向其发送SIGTERM,如下所示:
proc.terminate()
proc.join()
它并不慢:几分之一秒。 享受:)
答案 5 :(得分:0)
我的解决方法是:
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()
答案 6 :(得分:0)
我发现有一个派生自 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 秒),但您也可以在构造函数中传递该值。