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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18018033/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 09:44:10  来源:igfitidea点击:

How to stop a looping thread in Python?

pythonmultithreadingwxpython

提问by pedram

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.Threadclass. In this class it sleeps 60 seconds, the runs again until the application quits.

我有一个相当简单的程序,它可以 ping 一个单独的threading.Thread类中的指定主机。在这个类中它会休眠 60 秒,然后再次运行直到应用程序退出。

I'd like to implement a 'Stop' button in my wx.Frameto ask the looping thread to stop. It doesn't need to end the thread right away, it can just stop looping once it wakes up.

我想在我的中实现一个“停止”按钮wx.Frame来要求循环线程停止。它不需要立即结束线程,它可以在唤醒后停止循环。

Here is my threadingclass (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()

采纳答案by Jan Vlcinsky

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
    t.join()

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
  • driving parent process can assign to started thread the property "do_run" to False.
  • 线程有一个属性“do_run”,默认值 True
  • 驱动父进程可以将属性“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.Eventas function argument. It is by default False, but external process can "set it" (to True) and function can learn about it using wait(timeout)function.

另一种选择是threading.Event用作函数参数。它是默认的False,但外部进程可以“设置”(到True),函数可以使用wait(timeout)函数来了解它。

We can waitwith 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. stop_event.wait()blocks the event (and so the while loop) until release. It does not return a boolean value. Using stop_event.is_set()works instead.

编辑:我在 Python 3.6 中尝试过这个。stop_event.wait()阻止事件(以及 while 循环)直到释放。它不返回布尔值。使用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 doitwill not change at all, only the mainhandles 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()

回答by Mike Driscoll

This has been asked before on Stack. See the following links:

之前在 Stack 上已经问过这个问题。请参阅以下链接:

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.

基本上,您只需要使用停止函数设置线程,该函数设置线程将检查的标记值。在您的情况下,您将让循环中的某些内容检查哨兵值以查看它是否已更改,如果已更改,则循环可能会中断并且线程可能会死亡。

回答by pedram

I read the other questions on Stack but I was still a little confused on communicating across classes. Here is how I approached it:

我阅读了 Stack 上的其他问题,但我对跨类交流仍然有些困惑。这是我如何处理它:

I use a list to hold all my threads in the __init__method of my wxFrame class: self.threads = []

我使用一个列表将所有线程保存在__init__我的 wxFrame 类的方法中:self.threads = []

As recommended in How to stop a looping thread in Python?I use a signal in my thread class which is set to Truewhen initializing the threading class.

正如如何在 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()

and I can stop these threads by iterating over my threads:

我可以通过迭代我的线程来停止这些线程:

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

回答by Piotr Sawicki

I had a different approach. I've sub-classed a Thread class and in the constructor I've created an Event object. Then I've written custom join() method, which first sets this event and then calls a parent's version of itself.

我有不同的方法。我对 Thread 类进行了子类化,并在构造函数中创建了一个 Event 对象。然后我编写了自定义 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 类的使用太麻烦了。