簡體   English   中英

如何在 Python 中停止循環線程?

[英]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()

訣竅是,正在運行的線程可以附加其他屬性。 該解決方案基於以下假設:

  • 該線程有一個屬性“do_run”,默認值為True
  • 驅動父進程可以將屬性“do_run”分配給已啟動的線程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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM