Как прервать выполнение потока на Python в графическом приложении wxpyton?

#python #multithreading #wxpython

Вопрос:

Я пишу приложение с графическим интерфейсом Python 3.9, используя wxPython. Приложение позволяет пользователю ввести информацию и нажать кнопку «рассчитать». Это приведет к запуску продолжительного потока, который обновляет главное окно приложения обновлениями статуса. Приложение успешно запускает поток вычислений и остается отзывчивым.

Теперь я хотел бы добавить кнопку «прервать», которая остановит/отменит/прервет ранее запущенный поток, когда пользователи решат нажать кнопку «прервать».

Поискав некоторое время, я нашел приведенный ниже код на веб-сайте wxPython, на котором есть общая структура, которую я ищу. Это следует основной логике моего приложения в более простой форме. В нем отсутствует текущее обновление статуса графического интерфейса, поэтому я сделал одно дополнение для имитации обновлений статуса графического интерфейса — см. «lt;lt;lt;lt;lt; wx.PostEvent(self._notify_window, ResultEvent(i))

Однако после добавления этой строки кажется, что обработка событий прерывается, когда пользователь нажимает кнопку «стоп» во время запуска потока обработки. При нажатии кнопки «стоп» через 3-4 секунды поток подсчета в приведенном ниже примере кода продолжает выполняться и игнорирует событие кнопки «стоп».

Когда эта строка удаляется, функция «стоп» в приведенном ниже примере кода работает, и цикл прерывается.

Как я могу сохранить функцию обновления графического интерфейса с обновлениями статуса во время выполнения потока подсчета, а также использовать кнопку «остановить» для завершения активно работающего потока?

Я уверен, что есть способы оптимизировать приведенный ниже код. Пожалуйста, не стесняйтесь делиться своими мыслями/знаниями. Спасибо.

 import time from threading import * import wx  # Button definitions ID_START = wx.NewId() ID_STOP = wx.NewId()  # Define notification event for thread completion EVT_RESULT_ID = wx.NewId()  def EVT_RESULT(win, func):  """Define Result Event."""  win.Connect(-1, -1, EVT_RESULT_ID, func)  class ResultEvent(wx.PyEvent):  """Simple event to carry arbitrary result data."""  def __init__(self, data):  """Init Result Event."""  wx.PyEvent.__init__(self)  self.SetEventType(EVT_RESULT_ID)  self.data = data  # Thread class that executes processing class WorkerThread(Thread):  """Worker Thread Class."""  def __init__(self, notify_window):  """Init Worker Thread Class."""  Thread.__init__(self)  self._notify_window = notify_window  self._want_abort = 0  # This starts the thread running on creation, but you could  # also make the GUI thread responsible for calling this  #self.start()   def run(self):  """Run Worker Thread."""  # This is the code executing in the new thread. Simulation of  # a long process (well, 10s here) as a simple loop - you will  # need to structure your processing so that you periodically  # peek at the abort variable  for i in range(10):  if self._want_abort:  # Use a result of None to acknowledge the abort (of  # course you can use whatever you'd like or even  # a separate event type)  wx.PostEvent(self._notify_window, ResultEvent(None))  return  time.sleep(2)  wx.PostEvent(self._notify_window, ResultEvent(i)) lt;lt;lt;lt;lt;lt;lt; Added to simulate "status update" feedback of my application.    # Here's where the result would be returned (this is an  # example fixed result of the number 10, but it could be  # any Python object)  wx.PostEvent(self._notify_window, ResultEvent(10))   def abort(self):  """abort worker thread."""  # Method for use by main thread to signal an abort  self._want_abort = 1  # GUI Frame class that spins off the worker thread class MainFrame(wx.Frame):  """Class MainFrame."""  def __init__(self, parent, id):  """Create the MainFrame."""  wx.Frame.__init__(self, parent, id, 'Thread Test')   # Dumb sample frame with two buttons  wx.Button(self, ID_START, 'Start', pos=(0,0))  wx.Button(self, ID_STOP, 'Stop', pos=(0,50))  self.status = wx.StaticText(self, -1, '', pos=(0,100))   self.Bind(wx.EVT_BUTTON, self.OnStart, id=ID_START)  self.Bind(wx.EVT_BUTTON, self.OnStop, id=ID_STOP)   # Set up event handler for any worker thread results  EVT_RESULT(self,self.OnResult)   # And indicate we don't have a worker thread yet  self.worker = None   def OnStart(self, event):  """Start Computation."""  # Trigger the worker thread unless it's already busy  if not self.worker:  self.status.SetLabel('Starting computation')  self.worker = WorkerThread(self)  self.worker.start()   def OnStop(self, event):  """Stop Computation."""  # Flag the worker thread to stop if running  if self.worker:  self.status.SetLabel('Trying to abort computation')  self.worker.abort()   def OnResult(self, event):  """Show Result status."""  if event.data is None:  # Thread aborted (using our convention of None return)  self.status.SetLabel('Computation aborted')  else:  # Process results here  self.status.SetLabel('Computation Result: %s' % event.data)  # In either event, the worker is done  self.worker = None  class MainApp(wx.App):  """Class Main App."""  def OnInit(self):  """Init Main App."""  self.frame = MainFrame(None, -1)  self.frame.Show(True)  self.SetTopWindow(self.frame)  return True  if __name__ == '__main__':  app = MainApp(0)  app.MainLoop()  

Ответ №1:

Вы просто находитесь self.worker = None в неправильной точке.
Он должен быть установлен только None на остановку или когда он завершается естественным образом, кроме того, что код в порядке.
напр.

 import time from threading import * import wx  # Button definitions ID_START = wx.NewId() ID_STOP = wx.NewId()  # Define notification event for thread completion EVT_RESULT_ID = wx.NewId()  def EVT_RESULT(win, func):  """Define Result Event."""  win.Connect(-1, -1, EVT_RESULT_ID, func)  class ResultEvent(wx.PyEvent):  """Simple event to carry arbitrary result data."""  def __init__(self, data):  """Init Result Event."""  wx.PyEvent.__init__(self)  self.SetEventType(EVT_RESULT_ID)  self.data = data  # Thread class that executes processing class WorkerThread(Thread):  """Worker Thread Class."""  def __init__(self, notify_window):  """Init Worker Thread Class."""  Thread.__init__(self)  self._notify_window = notify_window  self._want_abort = 0  # This starts the thread running on creation, but you could  # also make the GUI thread responsible for calling this  #self.start()   def run(self):  """Run Worker Thread."""  # This is the code executing in the new thread. Simulation of  # a long process (well, 10s here) as a simple loop - you will  # need to structure your processing so that you periodically  # peek at the abort variable  for i in range(10):  if self._want_abort:  # Use a result of None to acknowledge the abort (of  # course you can use whatever you'd like or even  # a separate event type)  wx.PostEvent(self._notify_window, ResultEvent(None))  return  time.sleep(1)  wx.PostEvent(self._notify_window, ResultEvent(i)) # lt;lt;lt;lt;lt;lt;lt; Added to simulate "status update" feedback of my application.    # Here's where the result would be returned (this is an  # example fixed result of the number 10, but it could be  # any Python object)  time.sleep(2)  wx.PostEvent(self._notify_window, ResultEvent(10))   def abort(self):  """abort worker thread."""  # Method for use by main thread to signal an abort  self._want_abort = 1  # GUI Frame class that spins off the worker thread class MainFrame(wx.Frame):  """Class MainFrame."""  def __init__(self, parent, id):  """Create the MainFrame."""  wx.Frame.__init__(self, parent, id, 'Thread Test')   # Dumb sample frame with two buttons  wx.Button(self, ID_START, 'Start', pos=(0,0))  wx.Button(self, ID_STOP, 'Stop', pos=(0,50))  self.status = wx.StaticText(self, -1, '', pos=(0,100))   self.Bind(wx.EVT_BUTTON, self.OnStart, id=ID_START)  self.Bind(wx.EVT_BUTTON, self.OnStop, id=ID_STOP)   # Set up event handler for any worker thread results  EVT_RESULT(self,self.OnResult)   # And indicate we don't have a worker thread yet  self.worker = None   def OnStart(self, event):  """Start Computation."""  # Trigger the worker thread unless it's already busy  if self.worker:  return  else:  self.status.SetLabel('Starting computation')  self.worker = WorkerThread(self)  self.worker.start()  self.status.SetLabel('Computation started')   def OnStop(self, event):  """Stop Computation."""  # Flag the worker thread to stop if running  if self.worker:  self.status.SetLabel('Trying to abort computation')  self.worker.abort()  self.worker = None  else:  self.status.SetLabel('Computation not running')   def OnResult(self, event):  """Show Result status."""  if event.data is None:  # Thread aborted (using our convention of None return)  self.status.SetLabel('Computation aborted')  else:  # Process results here  self.status.SetLabel('Computation Result: %s' % event.data)  if event.data == 10:   self.worker = None  self.status.SetLabel('Computation Finished')  class MainApp(wx.App):  """Class Main App."""  def OnInit(self):  """Init Main App."""  self.frame = MainFrame(None, -1)  self.frame.Show(True)  self.SetTopWindow(self.frame)  return True  if __name__ == '__main__':  app = MainApp(0)  app.MainLoop()