【问题标题】:wxpython: Is it possible to stop the running program when I click the stop button?wxpython:当我单击停止按钮时,是否可以停止正在运行的程序?
【发布时间】:2019-05-22 12:12:49
【问题描述】:

假设我有一个需要一段时间才能完成的大型程序,当程序启动时,我发现某些参数设置不正确,所以我想停止它。我不想完全终止程序,而是希望停止运行计算,但仍然显示 wx.Frame。可能吗?下面是示例代码:

import wx
import time

class Test(wx.Frame):

    def __init__(self, title):
        super().__init__(None, title=title)
        self.panel = wx.Panel(self)

        self.initUI()
        self.Centre()

    def initUI(self):
        vbox = wx.BoxSizer(wx.VERTICAL)
        button1 = wx.Button(self.panel, label="START Calculation")
        button1.Bind(wx.EVT_BUTTON, self.start_test)

        button2 = wx.Button(self.panel, label="STOP Calculation")
        button2.Bind(wx.EVT_BUTTON, self.stop_test)

        vbox.Add(button1, 0, wx.ALL, 5)
        vbox.Add(button2, 0, wx.ALL, 5)
        self.panel.SetSizer(vbox)

    def start_test(self, e):
        for i in range(20000):
            print("Program is running...")
            time.sleep(5)

    def stop_test(self, e):
        print("How do I stop the test when click this button?")


if __name__ == '__main__':
    app = wx.App()
    Test("A large program").Show()
    app.MainLoop()

如程序所示,当我点击开始按钮时,程序运行。我希望我可以停止程序但仍然保留应用程序窗口。 现在,当我单击开始按钮时,停止按钮甚至无法单击。有没有可能达到我想要的目标?

【问题讨论】:

    标签: python python-3.x user-interface wxpython wxwidgets


    【解决方案1】:

    我只是稍微修改了您的代码。我添加了一个线程来运行计算方法,因此它不会阻塞主线程。

    import wx
    import time
    import threading
    
    class Test(wx.Frame):
    
        def __init__(self, title):
            super().__init__(None, title=title)
            self.panel = wx.Panel(self)
    
            self.initUI()
            self.Centre()
    
        def initUI(self):
            vbox = wx.BoxSizer(wx.VERTICAL)
            button1 = wx.Button(self.panel, label="START Calculation")
            button1.Bind(wx.EVT_BUTTON, self.start_test)
    
            button2 = wx.Button(self.panel, label="STOP Calculation")
            button2.Bind(wx.EVT_BUTTON, self.stop_test)
    
            vbox.Add(button1, 0, wx.ALL, 5)
            vbox.Add(button2, 0, wx.ALL, 5)
            self.panel.SetSizer(vbox)
            self.stop = False
    
        def create_thread(self, target):
            thread = threading.Thread(target=target)
            thread.daemon = True
            thread.start()
    
        def calculate(self):
            for i in range(20000):
                if self.stop:
                    break
                print("Program is running...")
                time.sleep(5)
            self.stop = False
    
        def start_test(self, e):
            self.create_thread(self.calculate)
    
        def stop_test(self, e):
            print("The calculation has been stopped!")
            self.stop = True
    
    
    if __name__ == '__main__':
        app = wx.App()
        Test("A large program").Show()
        app.MainLoop()
    

    如果 self.stop 为 True,它会从 foo 循环中跳出。这样,您只需单击两个按钮即可开始和停止计算。

    【讨论】:

    • 有没有办法直接杀死线程,可能是线程ID之类的?有了它,我不需要任何标志来插入我的主要执行代码。
    【解决方案2】:

    你不能用一个线程做到这一点。

    你必须在另一个线程中运行你的计算循环,并让停止按钮设置一个标志,以便循环在某个点停止(因为你不能强行杀死一个线程:Is there any way to kill a Thread?。TL;DR?答案是没有)

    类似这样的:

    import threading
    
    ...
    
      def run_code(self):
          for i in range(20000):
              print("Program is running...")
              time.sleep(5)
              if self.__stopped:
                 break
          self.__stopped = True
    
      def start_test(self, e):
          if not self.__stopped:
             self.__stopped = False
             threading.Thread(target=run_code).start()
    
      def stop_test(self, e):
          self.__stopped = True
    

    现在,当您单击“开始”时,它会在线程中启动 run_code 方法,因此它会进入主循环并且“停止”按钮处于活动状态。

    由于线程以另一种方法运行,您可以共享__stopped属性。

    要小心不时地调用time.sleep()(即使很小),因为线程在 Python 中不使用时间切片(因为大多数 Python 版本都实现了全局解释器锁)。正在运行的线程必须不时向主线程提供一些 CPU,否则将无法正常工作。好吧,你可以先试试看,看看。

    也不要在线程中使用wx 调用。使用wx.CallAfter 调度线程内的wx 调用,在主线程中执行。

    【讨论】:

    • 有没有更好的办法,因为计算可能不是for循环,可能是一大堆代码,到处放flag是不可能的。也许有办法直接杀死一个线程?
    • 我有一个关于线程的非专业问题:因为创建的线程不是主线程。如果计算部分是资源消耗的(例如需要大量的cpu功率),计算是否会和我没有添加线程的情况一样快,并使用我的原始程序?
    • 是的,前提是您插入的 sleep 不会花费太长时间/不会太频繁。每秒产生几毫秒(平均),这不会显示
    【解决方案3】:

    有些人似乎不喜欢这种方法,但我觉得它简单实用。
    使用Yield,它会暂时将控制权交还给主循环。
    这允许主循环仍然响应,即您的停止按钮可以在循环迭代之间激活。

    import wx
    import time
    
    class Test(wx.Frame):
    
        def __init__(self, title):
            super().__init__(None, title=title)
            self.panel = wx.Panel(self)
    
            self.initUI()
            self.Centre()
    
        def initUI(self):
            vbox = wx.BoxSizer(wx.VERTICAL)
            button1 = wx.Button(self.panel, label="START Calculation")
            button1.Bind(wx.EVT_BUTTON, self.start_test)
    
            button2 = wx.Button(self.panel, label="STOP Calculation")
            button2.Bind(wx.EVT_BUTTON, self.stop_test)
    
            vbox.Add(button1, 0, wx.ALL, 5)
            vbox.Add(button2, 0, wx.ALL, 5)
            self.panel.SetSizer(vbox)
    
        def start_test(self, e):
            self.running = 0
            loop = 0
            while self.running == 0:
                print("Program is running..."+str(loop))
                time.sleep(1)
                loop +=1
                wx.GetApp().Yield()
    
        def stop_test(self, e):
            print("Process has stopped")
            self.running = 1
    
    if __name__ == '__main__':
        app = wx.App()
        Test("A large program").Show()
        app.MainLoop()
    

    【讨论】:

    • 谢谢,但假设time.sleep(100)(这应该是大型程序所需的时间尺度)。此方法在 100 秒后才会响应。
    • 为什么是 100 秒?但是,如果您对长时间运行的任务进行编码,如果它每 100 秒只测试一次停止事件,那么您不妨将其作为一个单独的进程并kill 它关闭,如果按下停止按钮。跨度>
    猜你喜欢
    • 2019-05-12
    • 1970-01-01
    • 2013-02-20
    • 1970-01-01
    • 1970-01-01
    • 2015-04-25
    • 1970-01-01
    • 1970-01-01
    • 2011-06-12
    相关资源
    最近更新 更多