First time here? We are a friendly community of Power Systems Engineers. Check out the FAQ!
1 | initial version |
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()
2 | No.2 Revision |
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()