Блокировка потоков 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)