Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

wxPython & PSSE threading

I have been trying to develop a GUI using wxPython for a long-running PSSE process which remains responsive while PSSE is doing its work. I'm not at all experienced with multithreading in any programming language, so I've generally just been following the structure from the first script shown here: https://wiki.wxpython.org/LongRunningTasks

It has become clear to me that invoking PSSE through the psspy API is not the same as running standalone python code when it comes to multithreading.

Here is my general code structure and an example GUI. There are two buttons in this sample GUI as well as two text fields. The "Add" button just increments the first text field by 1 and only exists as a check to see if the GUI is responsive. The "Run" button calls a long running PSSE process in a separate worker thread (or so I hoped) and then updates the second text field to '999' when it's finished. The script runs outside PSSE, and only instantiates PSSE when it's called on by the user.

Here's the kicker: if I replace "longrunningpsse_process()" with a simple "time.sleep(10)", then the GUI is responsive as I would have expected. However, as soon as PSSE enters the picture, the GUI hangs whenever PSSE is working on something.


I would like to know if this sort of multi-threading is possible, or if something about the psspy API precludes this approach. Does anyone have any experience in getting something like this working well?

I am using PSSE 34.9.3 and Python 2.7.18 if that matters.

import wx
import os
import sys
import threading
import time

syspath = r"C:\Program Files (x86)\PTI\PSSE34\PSSPY27"
ospath = r"C:\Program Files (x86)\PTI\PSSE34\PSSBIN"
sys.path.append(syspath)
os.environ['PATH'] += ';' + ospath
os.environ['PATH'] += ';' + syspath

import psspy
import redirect

EVT_RESULT_ID = wx.NewId()

def main():
    app = wx.App(None)
    frame = main_frame(None, title='Test Multithreading')
    frame.SetSize(300, 100)
    frame.Show()
    app.MainLoop()

def EVT_RESULT(win, func):
    win.Connect(-1, -1, EVT_RESULT_ID, func)

class ResultEvent(wx.PyEvent):
    def __init__(self, data):
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_RESULT_ID)
        self.data = data

class WorkerThread(threading.Thread):
    def __init__(self, notify_window):
        threading.Thread.__init__(self)
        self._notify_window = notify_window

    def run(self):
        long_running_psse_process()
        wx.PostEvent(self._notify_window, ResultEvent(999))

class main_frame(wx.Frame):
    def __init__(self, parent, title):
        super(main_frame, self).__init__(parent, title=title,
                                         style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
        self.CenterOnScreen(direction=wx.HORIZONTAL)
        self.x = 0
        self.worker = None

        EVT_RESULT(self, self.on_finish)

        pnl = wx.Panel(self)
        sizer = wx.GridBagSizer(2, 2)

        button_add = wx.Button(pnl, label='Add', size=(90, 28))
        button_add.Bind(wx.EVT_BUTTON, self.on_add)
        sizer.Add(button_add, pos=(0, 0), border=10)

        self.x_text = wx.StaticText(pnl, label='0')
        sizer.Add(self.x_text, pos=(0, 1), border=10)

        button_run = wx.Button(pnl, label='Run', size=(90, 28))
        button_run.Bind(wx.EVT_BUTTON, self.on_run)
        sizer.Add(button_run, pos=(1, 0), border=10)

        self.y_text = wx.StaticText(pnl, label='0')
        sizer.Add(self.y_text, pos=(1, 1), border=10)

        pnl.SetSizer(sizer)
        sizer.Fit(self)

    def on_add(self, event):
        self.x += 1
        self.x_text.SetLabel(str(self.x))

    def on_run(self, event):
        if not self.worker:
            worker = WorkerThread(self)
            worker.start()

    def on_finish(self, event):
        self.y_text.SetLabel(str(event.data))
        self.worker = None

if __name__ == '__main__':
    main()

wxPython & PSSE threading

I have been trying to develop a GUI using wxPython for a long-running PSSE process which remains responsive while PSSE is doing its work. I'm not at all experienced with multithreading in any programming language, so I've generally just been following the structure from the first script shown here: https://wiki.wxpython.org/LongRunningTasks

It has become clear to me that invoking PSSE through the psspy API is not the same as running standalone python code when it comes to multithreading.

Here is my general code structure and an example GUI. There are two buttons in this sample GUI as well as two text fields. The "Add" button just increments the first text field by 1 and only exists as a check to see if the GUI is responsive. The "Run" button calls a long running PSSE process in a separate worker thread (or so I hoped) and then updates the second text field to '999' when it's finished. The script runs outside PSSE, and only instantiates PSSE when it's called on by the user.

Here's the kicker: if I replace "longrunningpsse_process()" long_running_psse_process() with a simple "time.sleep(10)", time.sleep(10), then the GUI is responsive as I would have expected. However, as soon as PSSE enters the picture, the GUI hangs whenever PSSE is working on something.


I would like to know if this sort of multi-threading is possible, or if something about the psspy API precludes this approach. Does anyone have any experience in getting something like this working well?

I am using PSSE 34.9.3 and Python 2.7.18 if that matters.

import wx
import os
import sys
import threading
import time

syspath = r"C:\Program Files (x86)\PTI\PSSE34\PSSPY27"
ospath = r"C:\Program Files (x86)\PTI\PSSE34\PSSBIN"
sys.path.append(syspath)
os.environ['PATH'] += ';' + ospath
os.environ['PATH'] += ';' + syspath

import psspy
import redirect

EVT_RESULT_ID = wx.NewId()

def main():
    app = wx.App(None)
    frame = main_frame(None, title='Test Multithreading')
    frame.SetSize(300, 100)
    frame.Show()
    app.MainLoop()

def EVT_RESULT(win, func):
    win.Connect(-1, -1, EVT_RESULT_ID, func)

class ResultEvent(wx.PyEvent):
    def __init__(self, data):
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_RESULT_ID)
        self.data = data

class WorkerThread(threading.Thread):
    def __init__(self, notify_window):
        threading.Thread.__init__(self)
        self._notify_window = notify_window

    def run(self):
        long_running_psse_process()
        wx.PostEvent(self._notify_window, ResultEvent(999))

class main_frame(wx.Frame):
    def __init__(self, parent, title):
        super(main_frame, self).__init__(parent, title=title,
                                         style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
        self.CenterOnScreen(direction=wx.HORIZONTAL)
        self.x = 0
        self.worker = None

        EVT_RESULT(self, self.on_finish)

        pnl = wx.Panel(self)
        sizer = wx.GridBagSizer(2, 2)

        button_add = wx.Button(pnl, label='Add', size=(90, 28))
        button_add.Bind(wx.EVT_BUTTON, self.on_add)
        sizer.Add(button_add, pos=(0, 0), border=10)

        self.x_text = wx.StaticText(pnl, label='0')
        sizer.Add(self.x_text, pos=(0, 1), border=10)

        button_run = wx.Button(pnl, label='Run', size=(90, 28))
        button_run.Bind(wx.EVT_BUTTON, self.on_run)
        sizer.Add(button_run, pos=(1, 0), border=10)

        self.y_text = wx.StaticText(pnl, label='0')
        sizer.Add(self.y_text, pos=(1, 1), border=10)

        pnl.SetSizer(sizer)
        sizer.Fit(self)

    def on_add(self, event):
        self.x += 1
        self.x_text.SetLabel(str(self.x))

    def on_run(self, event):
        if not self.worker:
            worker = WorkerThread(self)
            worker.start()

    def on_finish(self, event):
        self.y_text.SetLabel(str(event.data))
        self.worker = None

if __name__ == '__main__':
    main()