在执行过程中终止线程类

Terminate thread class mid-execution

提问人:newby73 提问时间:5/3/2023 更新时间:5/3/2023 访问量:55

问:

在此程序中,按下“启动计数器”时启动非阻塞线程。这将启动一个名为“CountingThread”的线程类。线程按应有的方式工作(递增 3 直到达到 12,中间休眠)。

但是,我正在尝试实现一个“停止计数器”按钮,无论线程执行多远,它都会终止线程。

CountingFrame.py

#-----------------------------------------------------------------------------#
import wx
import time
import threading
number = 0
#-----------------------------------------------------------------------------#

class CountingFrame(wx.Frame):      
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)

        # Attributes
        self._counter = wx.StaticText(self, label="0")

        # Layout
        sizer = wx.BoxSizer(wx.VERTICAL)
        start_button = wx.Button(self, wx.ID_ANY, "Start Counter")
        stop_button = wx.Button(self, wx.ID_ANY, "Stop Counter")
        sizer.AddMany([(start_button, 0, wx.ALIGN_CENTER),
                       (stop_button, 0, wx.ALIGN_CENTER),
                       ((15, 15), 0),
                       (self._counter, 0, wx.ALIGN_CENTER)])

        self.SetSizer(sizer)
        self.SetMinSize((300, 300))        
        
        # Event Handlers
        self.Bind(wx.EVT_BUTTON, self.OnStart, start_button)
        self.Bind(wx.EVT_BUTTON, self.OnStop, stop_button)
        self.Bind(EVT_COUNT, self.OnCount)

    def OnStart(self, evt):
        thread = CountingThread(self)
        thread.start()
        
    def OnStop(self, evt):
        CountingThread.stop()   ##### Need to replace this line #####

    def OnCount(self, evt):     
        val = str(int(self._counter.GetLabel()) + evt.GetValue())
        self._counter.SetLabel(val)

#-----------------------------------------------------------------------------#

## Runs on its own thread and calls CountEvent to increment num    
class CountingThread(threading.Thread):        
    def __init__(self, parent):
        """
        @param parent: The gui object that should recieve the value
        @param value: value to 'calculate' to
        """
        threading.Thread.__init__(self)
        self._parent = parent

    def run(self):
        """Overrides Thread.run. Don't call this directly its called internally
        when you call Thread.start().
        """
        limit = 5
        increment_amt = 1
        while increment_amt < limit:
            time.sleep(2) # our simulated calculation time
            evt = CountEvent(myEVT_COUNT)  
            wx.PostEvent(self._parent, evt)
            increment_amt +=1
    
#-----------------------------------------------------------------------------#
    
## Performs the event: increments num by 3
myEVT_COUNT = wx.NewEventType()
EVT_COUNT = wx.PyEventBinder(myEVT_COUNT)
class CountEvent(wx.PyCommandEvent): 
    """Event to signal that a count value is ready"""
    def __init__(self, eid):
        """Creates the event object"""
        wx.PyCommandEvent.__init__(self, eid)

    def GetValue(self):
        global number
        """Returns the value from the event.
        @return: the value of this event
        """
        temp = number + 3
        return (temp)

#-----------------------------------------------------------------------------#

if __name__ == '__main__':
    APP = wx.App(False)
    FRAME = CountingFrame(None)
    FRAME.Show()
    APP.MainLoop()

#-----------------------------------------------------------------------------#

我尝试实现一个计时器来检查全局变量的值,该值在单击stop_button时会发生变化。但是,老实说,我只是不确定如何停止线程周期,即使是从 CountingThread 类本身。

python 多线程 事件 wxpython

评论


答:

1赞 Rolf of Saxony 5/3/2023 #1

将 Stop 函数添加到线程中,用于切换变量。
使该变量成为语句的一部分。
例如
while

#-----------------------------------------------------------------------------#
import wx
import time
import threading
number = 0
#-----------------------------------------------------------------------------#

class CountingFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)

        # Attributes
        self._counter = wx.StaticText(self, label="0")

        # Layout
        sizer = wx.BoxSizer(wx.VERTICAL)
        start_button = wx.Button(self, wx.ID_ANY, "Start Counter")
        stop_button = wx.Button(self, wx.ID_ANY, "Stop Counter")
        sizer.AddMany([(start_button, 0, wx.ALIGN_CENTER),
                       (stop_button, 0, wx.ALIGN_CENTER),
                       ((15, 15), 0),
                       (self._counter, 0, wx.ALIGN_CENTER)])

        self.SetSizer(sizer)
        self.SetMinSize((300, 300))

        # Event Handlers
        self.Bind(wx.EVT_BUTTON, self.OnStart, start_button)
        self.Bind(wx.EVT_BUTTON, self.OnStop, stop_button)
        self.Bind(EVT_COUNT, self.OnCount)

    def OnStart(self, evt):
        self.thread = CountingThread(self)
        self.thread.start()

    def OnStop(self, evt):
        self.thread.stop()   ##### Need to replace this line #####
        self.thread.join()
        print("Thread Terminated")

    def OnCount(self, evt):
        val = str(int(self._counter.GetLabel()) + evt.GetValue())
        self._counter.SetLabel(val)

#-----------------------------------------------------------------------------#

## Runs on its own thread and calls CountEvent to increment num
class CountingThread(threading.Thread):
    def __init__(self, parent):
        """
        @param parent: The gui object that should recieve the value
        @param value: value to 'calculate' to
        """
        threading.Thread.__init__(self)
        self._parent = parent
        self.terminate = False

    def run(self):
        """Overrides Thread.run. Don't call this directly its called internally
        when you call Thread.start().
        """
        limit = 20
        increment_amt = 1
        while increment_amt < limit and self.terminate is False:
            time.sleep(1) # our simulated calculation time
            evt = CountEvent(myEVT_COUNT)
            wx.PostEvent(self._parent, evt)
            increment_amt +=1
        return

    def stop(self):
        print("Stop")
        self.terminate = True

#-----------------------------------------------------------------------------#

## Performs the event: increments num by 3
myEVT_COUNT = wx.NewEventType()
EVT_COUNT = wx.PyEventBinder(myEVT_COUNT)
class CountEvent(wx.PyCommandEvent):
    """Event to signal that a count value is ready"""
    def __init__(self, eid):
        """Creates the event object"""
        wx.PyCommandEvent.__init__(self, eid)

    def GetValue(self):
        global number
        """Returns the value from the event.
        @return: the value of this event
        """
        temp = number + 3
        return (temp)

#-----------------------------------------------------------------------------#

if __name__ == '__main__':
    APP = wx.App(False)
    FRAME = CountingFrame(None)
    FRAME.Show()
    APP.MainLoop()

#-----------------------------------------------------------------------------#
1赞 Solomon Slow #2

对萨克森州罗尔夫的回答进行一些阐述:

有些语言,包括 Python,无法让一个线程杀死另一个线程。那是因为这样做几乎总是一个错误。事实上,如果线程 V 在执行过程中的任何时候锁定了互斥锁,那么线程 K(“杀手”)杀死线程 V(“受害者”)总是错误的。

你可能会问,为什么 kill 操作不能自动解锁线程 V 持有的任何互斥锁?

线程通过改变共享数据来相互通信,通常重要的是,当一个线程正在更新共享数据时,不要让其他线程查看共享数据。它可能会使程序崩溃,或者更糟的是,如果某个线程在更改只完成一半时瞥见了共享数据。使用互斥锁的全部意义在于防止任何线程在其他线程更改共享数据时看到共享数据。

那么,如果在线程更改内容的一半完成时终止线程,会发生什么情况?自动解锁互斥锁会让其他线程看到半途而废的更改。不解锁互斥锁将阻止任何其他线程再次使用该数据。这两种方式都不是赢家。

一些较旧的系统允许程序中的一个线程杀死另一个线程,它们相信您会明智地使用这种能力。其他的,主要是较新的系统,包括Python,根本不允许你这样做。


线程应始终相互协作。如果线程 K 发现了线程 V 应亡的原因,那么它应该要求线程 V 清理它可能正在制造的任何混乱,并善意地自杀。而且,当然,线程 V 应该完全按照要求执行。