Как я могу заставить поток отвечать на сообщение очереди присоединения, чтобы обновить мой пользовательский интерфейс wx Phoenix?

У меня есть приложение с длительным фоновым процессом (daemon) и пользовательским интерфейсом на основе wx. Фоновый процесс помещает сообщения в multiprocessing.JoinableQueue(), из которого пользовательский интерфейс должен читать, чтобы получать обновления состояния процесса демона во время его работы. Проблема, с которой я сталкиваюсь, заключается в том, что в то время как экземпляр AwaitStatusReportThreadClass передает сообщение в пользовательский интерфейс, он заставляет пользовательский интерфейс перестать отвечать на запросы. Если вы запустите приведенный ниже код, а затем попытаетесь изменить размер окна, приложение покажет, что оно не отвечает.

Я запускаю это на Windows 7, используя Python27 32bit, wxPython 4.0.0a3 (Phoenix)

Мое приложение структурировано с помощью различных файлов Python, но я смог воспроизвести ошибку с помощью приведенного ниже кода. Это довольно просто. Я создал класс с именем AwaitStatusReportThreadClass, чтобы использовать методы pub/sub, описанные здесь, для обновления пользовательского интерфейса. Метод init этого класса имеет бесконечный цикл, который проверяет сообщения о состоянии. Когда он найден, он обновляет сообщение StatusBar.

import multiprocessing
import threading
import wx
import sys
from wx.lib.pubsub import pub


class AwaitStatusReportThreadClass(threading.Thread):
    """This class should pass messages to the UI class"""
    def __init__(self, messageQueue):
        """Init worker Thread Class"""
        self.messageQueue = messageQueue

        threading.Thread.__init__(self)
        self.start()

    def run(self):
        """This code executes when the thread is run"""

        KeepRunningStatusThread = True
        while KeepRunningStatusThread:
            sys.stdout.flush()

            try:
                msg = self.messageQueue.get()
                self.messageQueue.task_done()
            except:
                pass

            if msg == "Shutdown":
                # Kill this thread
                KeepRunningStatusThread = False

            else:
                pub.sendMessage("UI", msg=msg)


class UI2(wx.Frame):
    """This class is the UI"""
    def __init__(self, taskQueue, messageQueue, stopQueue, parent=None):

        self.taskQueue = taskQueue
        self.messageQueue = messageQueue
        self.stopQueue = stopQueue

        wx.Frame.__init__(self, parent, title="TestApp")

        #Main panel
        sizerMain = wx.BoxSizer(wx.VERTICAL)

        # Add the status bar
        panelStatusBar = wx.Panel(self)
        sizerStatusBar = wx.BoxSizer(wx.HORIZONTAL)
        panelStatusBar.SetSizer(sizerStatusBar)

        self.StatusBar_Main = wx.StatusBar(panelStatusBar, wx.NewId())
        sizerStatusBar.Add(self.StatusBar_Main, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 2)

        #Add the status bar sizer to the main sizer
        sizerMain.Add(panelStatusBar, 0, wx.EXPAND)

        #Add the progress bar
        panelProgressBar = wx.Panel(self)
        sizerProgressBar = wx.BoxSizer(wx.HORIZONTAL)
        panelProgressBar.SetSizer(sizerProgressBar)
        self.Gauge_ProgressBar = wx.Gauge(panelProgressBar, wx.NewId())
        sizerProgressBar.Add(self.Gauge_ProgressBar, 1, wx.EXPAND)
        sizerMain.Add(panelProgressBar,0,wx.EXPAND)

        #Layout the frame
        self.SetSizer(sizerMain)
        self.SetAutoLayout(1)
        sizerMain.Fit(self)
        self.Layout()
        self.Show(show=True)

        #Subscribe to messages from the messageQueue
        pub.subscribe(self.HandleStatusUpdate, "UI")
        AwaitStatusReportThreadClass(self.messageQueue)

    def HandleStatusUpdate(self, msg):
        """
        This def updates the UI from a pubsub subscription

        """
        StatusBar = self.StatusBar_Main
        StatusBar.PushStatusText(msg)


if __name__ == "__main__":

    #Make multiprocessing work when app is frozen
    multiprocessing.freeze_support()

    taskQueue = multiprocessing.JoinableQueue() #Specifies tasks to be completed by the GenInst process
    messageQueue = multiprocessing.JoinableQueue() #Holds status messages / progress messages to update the message zone and progress bar in the UI
    stopQueue = multiprocessing.JoinableQueue() #Allows cancel operation button to function

    messageQueue.put("Please wait while the GenInst background process starts...")

    #Start the UI
    app = wx.App(False)
    frame = UI2(taskQueue=taskQueue, messageQueue=messageQueue, stopQueue=stopQueue)
    app.MainLoop()

Любой совет?

1 ответ

Ну, решение было простым. Я оставлю это здесь на случай, если это поможет кому-нибудь в будущем.

Все, что требовалось, - это реструктурировать класс AwaitStatusReportThreadClass, чтобы использовать wx.CallAfter для публикации сообщения. Я добавил функцию postMessage в класс и вызвал ее, используя wx.CallAfter(self.postMessage, msg)

class AwaitStatusReportThreadClass(threading.Thread):
    """This class should pass messages to the UI class"""
    def __init__(self, messageQueue):
        """Init worker Thread Class"""
        self.messageQueue = messageQueue

        threading.Thread.__init__(self)
        self.start()

    def run(self):
        """This code executes when the thread is run"""

        KeepRunningStatusThread = True
        while KeepRunningStatusThread:
            sys.stdout.flush()

            try:
                msg = self.messageQueue.get()
                self.messageQueue.task_done()
                wx.CallAfter(self.postMessage, msg)

            except:
                pub.sendMessage("Failed")         

    def postMessage(self, msg):
        pub.sendMessage("UI", msg=msg)
Другие вопросы по тегам