简体   繁体   English

如何在 Python 中停止循环线程?

[英]How to stop a looping thread in Python?

What's the proper way to tell a looping thread to stop looping?告诉循环线程停止循环的正确方法是什么?

I have a fairly simple program that pings a specified host in a separate threading.Thread class.我有一个相当简单的程序,可以在单独的threading.Thread类中 ping 指定的主机。 In this class it sleeps 60 seconds, the runs again until the application quits.在这个类中它会休眠 60 秒,然后再次运行直到应用程序退出。

I'd like to implement a 'Stop' button in my wx.Frame to ask the looping thread to stop.我想在我的wx.Frame实现一个“停止”按钮来要求循环线程停止。 It doesn't need to end the thread right away, it can just stop looping once it wakes up.它不需要立即结束线程,它可以在唤醒后停止循环。

Here is my threading class (note: I haven't implemented looping yet, but it would likely fall under the run method in PingAssets)这是我的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)

And in my wxPyhton Frame I have this function called from a Start button:在我的 wxPyhton 框架中,我从“开始”按钮调用了这个函数:

def CheckAsset(self, asset):
        self.count += 1
        thread = PingAssets(self.count, asset, self)
        self.threads.append(thread)
        thread.start()

Threaded stoppable function螺纹可停止功能

Instead of subclassing threading.Thread , one can modify the function to allow stopping by a flag.除了继承threading.Thread ,还可以修改该函数以允许通过标志停止。

We need an object, accessible to running function, to which we set the flag to stop running.我们需要一个可以访问运行函数的对象,我们将标志设置为停止运行。

We can use threading.currentThread() object.我们可以使用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()

The trick is, that the running thread can have attached additional properties.诀窍是,正在运行的线程可以附加其他属性。 The solution builds on assumptions:该解决方案基于以下假设:

  • the thread has a property "do_run" with default value True该线程有一个属性“do_run”,默认值为True
  • driving parent process can assign to started thread the property "do_run" to False .驱动父进程可以将属性“do_run”分配给已启动的线程False

Running the code, we get following output:运行代码,我们得到以下输出:

$ 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药丸杀死 - 使用事件

Other alternative is to use threading.Event as function argument.其他替代方法是使用threading.Event作为函数参数。 It is by default False , but external process can "set it" (to True ) and function can learn about it using wait(timeout) function.它默认为False ,但外部进程可以“设置它”(为True )并且函数可以使用wait(timeout)函数了解它。

We can wait with zero timeout, but we can also use it as the sleeping timer (used below).我们可以在零超时的情况下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()

Edit: I tried this in Python 3.6.编辑:我在 Python 3.6 中尝试过这个。 stop_event.wait() blocks the event (and so the while loop) until release. stop_event.wait()阻止事件(以及 while 循环)直到释放。 It does not return a boolean value.它不返回布尔值。 Using stop_event.is_set() works instead.使用stop_event.is_set()代替。

Stopping multiple threads with one pill用一颗药丸停止多线程

Advantage of pill to kill is better seen, if we have to stop multiple threads at once, as one pill will work for all.药丸杀死的优势更明显,如果我们必须一次停止多个线程,因为一颗药丸对所有线程都有效。

The doit will not change at all, only the main handles the threads a bit differently. 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()

This has been asked before on Stack.之前在 Stack 上已经问过这个问题。 See the following links:请参阅以下链接:

Basically you just need to set up the thread with a stop function that sets a sentinel value that the thread will check.基本上,您只需要使用停止函数设置线程,该函数设置线程将检查的标记值。 In your case, you'll have the something in your loop check the sentinel value to see if it's changed and if it has, the loop can break and the thread can die.在您的情况下,您将让循环中的某些内容检查哨兵值以查看它是否已更改,如果已更改,则循环可能会中断并且线程可能会死亡。

I read the other questions on Stack but I was still a little confused on communicating across classes.我阅读了 Stack 上的其他问题,但我对跨类交流仍然有些困惑。 Here is how I approached it:这是我如何处理它:

I use a list to hold all my threads in the __init__ method of my wxFrame class: self.threads = []我使用一个列表将所有线程保存在 wxFrame 类的__init__方法中: self.threads = []

As recommended in How to stop a looping thread in Python?正如如何在 Python 中停止循环线程中所推荐的那样 I use a signal in my thread class which is set to True when initializing the threading class.我在线程类中使用了一个信号,该信号在初始化线程类时设置为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()

and I can stop these threads by iterating over my threads:我可以通过迭代我的线程来停止这些线程:

def OnStop(self, e):
        for t in self.threads:
            t.signal = False

I had a different approach.我有不同的方法。 I've sub-classed a Thread class and in the constructor I've created an Event object.我对 Thread 类进行了子类化,并在构造函数中创建了一个 Event 对象。 Then I've written custom join() method, which first sets this event and then calls a parent's version of itself.然后我编写了自定义 join() 方法,该方法首先设置此事件,然后调用自身的父版本。

Here is my class, I'm using for serial port communication in wxPython app:这是我的课程,我在 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()

The Queue is used for sending messages to the port and main loop takes responses back.队列用于向端口发送消息,主循环接收响应。 I've used no serial.readline() method, because of different end-line char, and I have found the usage of io classes to be too much fuss.我没有使用 serial.readline() 方法,因为不同的结束行字符,我发现 io 类的使用太麻烦了。

Depends on what you run in that thread.取决于您在该线程中运行的内容。 If that's your code, then you can implement a stop condition (see other answers).如果那是您的代码,那么您可以实现停止条件(请参阅其他答案)。

However, if what you want is to run someone else's code, then you should fork and start a process.然而,如果你想要运行别人的代码,那么你应该fork并启动一个进程。 Like this:像这样:

import multiprocessing
proc = multiprocessing.Process(target=your_proc_function, args=())
proc.start()

now, whenever you want to stop that process, send it a SIGTERM like this:现在,每当您想停止该进程时,请向其发送一个 SIGTERM,如下所示:

proc.terminate()
proc.join()

And it's not slow: fractions of a second.而且它并不慢:几分之一秒。 Enjoy :)享受 :)

My solution is:我的解决办法是:

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()

I find it useful to have a class, derived from threading.Thread , to encapsulate my thread functionality.我发现有一个派生自threading.Thread的类来封装我的线程功能很有用。 You simply provide your own main loop in an overridden version of run() in this class.您只需在此类中的run()覆盖版本中提供您自己的主循环。 Calling start() arranges for the object's run() method to be invoked in a separate thread.调用start()安排在单独的线程中调用对象的run()方法。

Inside the main loop, periodically check whether a threading.Event has been set.在主循环内,定期检查是否设置了threading.Event Such an event is thread-safe.这样的事件是线程安全的。

Inside this class, you have your own join() method that sets the stop event object before calling the join() method of the base class.在这个类中,您有自己的join()方法,该方法在调用基类的join()方法之前设置停止事件对象。 It can optionally take a time value to pass to the base class's join() method to ensure your thread is terminated in a short amount of time.可以选择将时间值传递给基类的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

Having a small sleep inside the main loop before checking the threading.Event is less CPU intensive than looping continuously.在检查threading.Event之前,在主循环内有一个小的睡眠。事件比连续循环占用更少的 CPU。 You can have a default sleep time (eg 0.1s), but you can also pass the value in the constructor.您可以有一个默认的睡眠时间(例如 0.1 秒),但您也可以在构造函数中传递该值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM