假设我有一个大型程序,需要一段时间才能完成,所以当程序启动时,我发现某些参数未正确设置,因此我想停止它。而不是完全终止程序,我希望运行的计算停止,但是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()
如程序所示,当我单击开始按钮时,程序将运行。我希望我可以停止该程序,但仍然保留应用程序窗口。目前,当我单击开始按钮时,甚至无法单击停止按钮。是否可以实现我想要的目标?
答案 0 :(得分:1)
您无法通过单个线程来做到这一点。
您必须在另一个线程中运行计算循环,并让停止按钮设置一个标志,这样循环才能在某个时间点停止(因为您不能强行杀死线程: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调用,以在主线程中执行。
答案 1 :(得分:1)
我只是稍微修改了您的代码。我添加了一个线程来运行calculate方法,因此它不会阻塞主线程。
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,它将从foor循环中退出。这样,您只需单击两个按钮即可开始和停止计算。
答案 2 :(得分:0)
有些人似乎不赞成这种方法,但我发现它简单而有用。
利用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()