Блокировка потоков wxPython
Это в вилке Phoenix wxPython.
Я пытаюсь запустить пару потоков в интересах не блокирования графического интерфейса.
Два моих потока работают нормально, но другой, похоже, не попадает в связанную функцию результата. Я могу сказать, что он работает, он просто не кажется должным образом размещать событие.
Здесь функция результата для основных потоков вычислений:
def on_status_result(self, event):
if not self.panel.progress_bar.GetRange():
self.panel.progress_bar.SetRange(event.data.parcel_count)
self.panel.progress_bar.SetValue(event.data.current_parcel)
self.panel.status_label.SetLabel(event.data.message)
Вот как я их связываю:
from wx.lib.pubsub.core import Publisher
PUB = Publisher()
Вот как я привязываю метод:
def post_event(message, data):
wx.CallAfter(lambda *a: Publisher().sendMessage(message, data=data))
А вот темы. Первый не работает, но второй второй:
class PrepareThread(threading.Thread):
def __init__(self, notify_window):
threading.Thread.__init__(self)
self._notify_window = notify_window
self._want_abort = False
def run(self):
while not self._want_abort:
for status in prepare_collection(DATABASE, self._previous_id, self._current_id, self._year, self._col_type,
self._lock):
post_event('prepare.running', status)
post_event('prepare.complete', None)
return None
def abort(self):
self._want_abort = True
class SetupThread(threading.Thread):
def __init__(self, notify_window):
threading.Thread.__init__(self)
self._notify_window = notify_window
self._want_abort = False
def run(self):
while not self._want_abort:
do_more_stuff_with_the_database()
return None
def abort(self):
self._want_abort = True
class LatestCollectionsThread(threading.Thread):
def __init__(self, notify_window):
threading.Thread.__init__(self)
self._notify_window = notify_window
self._want_abort = False
def run(self):
while not self._want_abort:
do_stuff_with_my_database()
return None
def abort(self):
self._want_abort = True
prepare_collection
- это функция, которая дает объекты Status
, которые выглядят следующим образом:
class Status:
def __init__(self, parcel_count, current_parcel, total, message):
self.parcel_count = parcel_count
self.current_parcel = current_parcel
self.total = total
self.message = message
Вот как я создаю/запускаю/подписываю PrepareThread:
MainForm(wx.Form):
prepare_thread = PrepareThread(self)
prepare_thread.start()
self.pub = Publisher()
self.pub.subscribe(self.on_status_result, 'prepare.running')
self.pub.subscribe(self.on_status_result, 'prepare.complete')
def on_status_result(self, event):
if not self.panel.progress_bar.GetRange():
self.panel.progress_bar.SetRange(event.data.parcel_count)
self.panel.progress_bar.SetValue(event.data.current_parcel)
self.panel.status_label.SetLabel(event.data.message)
Я пробовал стирать prepare_collection
с помощью range(10)
, но я до сих пор не ударил обработчик событий.
Ответы
Ответ 1
Это может быть довольно привлекательный ответ, и немного сложно определить, какой из ваших фрагментов у вас есть в каждой части вашего кода (то есть, какие файлы они все живут). Я предполагаю, что вы хотите сохранить способ pubsub
сделать это (что я считаю хорошей идеей). Если структура вашего реального проекта очень сложна, вам может потребоваться более сложное управление Publisher
, чем я здесь использую - дайте мне знать...
Вот так: я сначала поставлю спойлер - здесь одно файловое решение за то, что вам кажется, - панель с кнопкой для запуска готовой нити, строки состояния и обработчика для завершения подготовки. Протестировано с помощью wxPython Phoenix 64-битного Python 3 и старомодного wxPython на Python 2.7. Как в Windows, так и в случае необходимости я могу перевернуть его в Linux.
Подведение итогов важных (не котельных таблиц) бит этого файла
Вам нужен единственный объект Publisher
, на который ваши потоки отправляют сообщения, и ваш основной поток (MainForm
в вашем примере, я думаю) подписывается. Вы могли бы управлять Publisher
для каждого потока, но я думаю, что здесь вам нужен только один для PrepareThread
, поэтому я собираюсь пойти с этой моделью на данный момент.
В верхней части файла используйте
from wx.lib.pubsub import pub
Это позволяет pubsub
управлять созданием экземпляра одного объекта Publisher
.
В своем потоке, как вы это делаете, публикуйте там сообщения - небольшое исправление для вашего помощника post_event
:
def post_event(message, data):
wx.CallAfter(lambda *a: pub.sendMessage(message, data=data))
В основной теме - подписаться на эти сообщения. Я бы сказал, что обычно проще иметь одного обработчика на сообщение, вместо того, чтобы отправлять два разных сообщения одному и тому же обработчику, как и вы, поэтому я пошел на
pub.subscribe(self.on_status_result, 'prepare.running')
pub.subscribe(self.on_status_finished, 'prepare.complete')
Вы можете оставить on_status_result
как есть и определить аналогичный on_status_finished
. В моем примере у меня было это позже, что позволило создать новую кнопку, позволяющую вам выполнять определенную работу.
N.B. Вам нужно быть осторожным при указании полезной нагрузки ваших сообщений - pubsub
содержит немного информации о том, что она ожидает там, и это меня поймало вначале.
P.S. В конце подготовки этого ответа я нашел этот пост в блоге. В нем говорится что-то похожее на то, что у меня выше, поэтому я не буду воспроизводить его, но они используют другой метод создания экземпляра Publisher()
, как ваш оригинальный пример, что подразумевает, что это тоже должно работать. Вы можете предпочесть формулировку там. Simlarly - вы можете найти эту страницу wxPython wiki.
Ответ 2
проблема заключается в том, что система событий заканчивает вызов функции обновления (обработчика событий) из самих потоков, вы почти никогда этого не делаете (в основном вы оказываетесь в странных условиях гонки и артефактах)... всегда делайте обратный вызов в основном потоке.
wxPython учитывает это, и любые методы, вызванные с wx.CallAfter, будут вызываться из основного цикла программы, который всегда работает в основном потоке. это в сочетании с модулем wx.pubsub позволяет вам легко создавать свою собственную фреймворк событий... что-то вроде этого
def MyPostEvent(event_name,event_data):
#just a helper that triggers the event with wx.CallAfter
wx.CallAfter(lambda *a:Publisher().sendMessage(event_name,data=event_data))
#then to post an event
MyPostEvent("some_event.i_made_up",{"payload":True})
#then in your main thread subscribe
def OnEventHandler(evt):
print "EVT.data",evt.data
pub = Publisher()
pub.subscribe("some_event.i_made_up",OnEventHandler)