问题
I'm building an application with wxPython and a Deep Learning model that I created using Tensorflow. The design pattern that I'm using is MVC. My problem is that the deep learning model is very heavy and it takes a very long time to load (like ~2 minutes) and in the meantime the GUI hangs. I created a sample code that describes the process. Here is what the GUI looks like while loading:
enter image description here
and this is what the GUI looks like after the loading: enter image description here
The question is how do I get the application to run while the model is loading? I would also like to add a status line to the GUI indicating that the model is loading or already loaded.
I'm adding the sample code that shows how my application is built.
import wx
import time
class Model:
def __init__(self):
'''This part is simulating the loading of tensorflow'''
x = 0
while x < 15:
time.sleep(1)
print(x)
x += 1
class View(wx.Frame):
def __init__(self, parent, title):
super(View, self).__init__(parent, title=title, size=(400, 400))
self.InitUI()
def InitUI(self):
# Defines the GUI controls
masterPanel = wx.Panel(self)
masterPanel.SetBackgroundColour("gold")
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.fgs = wx.FlexGridSizer(6, 2, 10, 25)
id = wx.StaticText(self, label="ID:")
firstName = wx.StaticText(self, label="First name:")
lastName = wx.StaticText(self, label="Last name:")
self.idTc = wx.TextCtrl(self)
self.firstNameTc = wx.TextCtrl(self)
self.lastNameTc = wx.TextCtrl(self)
self.fgs.AddMany([id, (self.idTc, 1, wx.EXPAND),
firstName, (self.firstNameTc, 1, wx.EXPAND),
lastName, (self.lastNameTc, 1, wx.EXPAND)])
self.vbox.Add(self.fgs, proportion=1, flag=wx.ALL | wx.EXPAND,
border=15)
self.SetSizer(self.vbox)
self.vbox.Fit(self)
self.Layout()
class Controller:
def __init__(self):
self.view = View(None, title='Test')
self.view.Show()
self.model = Model()
def main():
app = wx.App()
controller = Controller()
app.MainLoop()
if __name__ == '__main__':
main()
回答1:
You should call wx.GetApp().Yield()
after the self.view.Show()
command, which releases control momentarily to the MainLoop
.
If the model load code is performed in increments, you can call Yield periodically during the load as well.
Below is a simple method of informing the user that something is going on. If you wanted the option of cancelling the model load, you would have to wrap it in a dialog, assuming that it is loaded incrementally.
import wx
import time
class Model:
def __init__(self):
'''This part is simulating the loading of tensorflow'''
x = 0
#If the model load is perform in increments you could call wx.Yield
# between the increments.
while x < 15:
time.sleep(1)
wx.GetApp().Yield()
print(x)
x += 1
class View(wx.Frame):
def __init__(self, parent, title):
super(View, self).__init__(parent, title=title, size=(400, 400))
self.InitUI()
def InitUI(self):
# Defines the GUI controls
#masterPanel = wx.Panel(self)
#masterPanel.SetBackgroundColour("gold")
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.fgs = wx.FlexGridSizer(6, 2, 10, 25)
id = wx.StaticText(self, label="ID:")
firstName = wx.StaticText(self, label="First name:")
lastName = wx.StaticText(self, label="Last name:")
self.idTc = wx.TextCtrl(self)
self.firstNameTc = wx.TextCtrl(self)
self.lastNameTc = wx.TextCtrl(self)
self.fgs.AddMany([id, (self.idTc, 1, wx.EXPAND),
firstName, (self.firstNameTc, 1, wx.EXPAND),
lastName, (self.lastNameTc, 1, wx.EXPAND)])
self.vbox.Add(self.fgs, proportion=1, flag=wx.ALL | wx.EXPAND,
border=15)
self.CreateStatusBar() # A Statusbar in the bottom of the window
self.StatusBar.SetStatusText("No model loaded", 0)
self.SetSizer(self.vbox)
self.vbox.Fit(self)
self.Layout()
class Controller:
def __init__(self):
self.view = View(None, title='Test')
self.view.Show()
self.view.SetStatusText("Model loading", 0)
wait = wx.BusyInfo("Please wait, loading model...")
#Optionally add parent to centre message on self.view
#wait = wx.BusyInfo("Please wait, loading model...", parent=self.view)
wx.GetApp().Yield()
self.model = Model()
self.view.SetStatusText("Model loaded", 0)
del wait
def main():
app = wx.App()
controller = Controller()
app.MainLoop()
if __name__ == '__main__':
main()
回答2:
You could use the _thread
module and the PyPubSub
module to keep the main thread completely functional during the loading of the model.
However, keep in mind that if you have a wx.Button
in the GUI bound to method A
and method A
requires the full model to be loaded to properly run, then users will be able to click the wx.Button
and the program will probably hang because the model is still not fully loaded. If this is the case, you could use the methods Disable()
(while the model is being loaded) and Enable()
(after the model is loaded) to prevent this.
Code with comments (####).
import wx
import time
import _thread
from pubsub import pub
class Model:
def __init__(self):
'''This part is simulating the loading of tensorflow'''
x = 0
while x < 15:
time.sleep(1)
print(x)
#### This is how you broadcast the 'Loading' message
#### from a different thread.
wx.CallAfter(pub.sendMessage, 'Loading', x=x)
x += 1
#### The same as before
wx.CallAfter(pub.sendMessage, 'Loading', x=x)
class View(wx.Frame):
def __init__(self, parent, title):
super(View, self).__init__(parent, title=title, size=(400, 400))
self.InitUI()
def InitUI(self):
# Defines the GUI controls
#### It needed to set the size of the panel to cover the frame
#### because it was not covering the entire frame before
masterPanel = wx.Panel(self, size=(400, 400))
masterPanel.SetBackgroundColour("gold")
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.fgs = wx.FlexGridSizer(6, 2, 10, 25)
id = wx.StaticText(self, label="ID:")
firstName = wx.StaticText(self, label="First name:")
lastName = wx.StaticText(self, label="Last name:")
self.idTc = wx.TextCtrl(self)
self.firstNameTc = wx.TextCtrl(self)
self.lastNameTc = wx.TextCtrl(self)
self.fgs.AddMany([id, (self.idTc, 1, wx.EXPAND),
firstName, (self.firstNameTc, 1, wx.EXPAND),
lastName, (self.lastNameTc, 1, wx.EXPAND)])
self.vbox.Add(self.fgs, proportion=1, flag=wx.ALL | wx.EXPAND,
border=15)
self.SetSizer(self.vbox)
self.vbox.Fit(self)
self.Layout()
#### Create status bar to show loading progress.
self.statusbar = self.CreateStatusBar(1)
self.statusbar.SetStatusText('Loading model')
#### Set the size of the window because the status bar steals space
#### in the height direction.
self.SetSize(wx.DefaultCoord, 160)
#### Subscribe the class to the message 'Loading'. This means that every
#### time the meassage 'Loading' is broadcast the method
#### ShowLoadProgress will be executed.
pub.subscribe(self.ShowLoadProgress, 'Loading')
#### Start the thread that will load the model
_thread.start_new_thread(self.LoadModel, ('test',))
def LoadModel(self, test):
"""
Load the Model
"""
#### Change depending on how exactly are you going to create/load the
#### model
self.model = Model()
def ShowLoadProgress(self, x):
"""
Show the loading progress
"""
if x < 15:
self.statusbar.SetStatusText('Loading progress: ' + str(x))
else:
self.statusbar.SetStatusText('All loaded')
class Controller:
def __init__(self):
self.view = View(None, title='Test')
self.view.Show()
#### The line below is not needed now because the model is
#### loaded now from the thread started in View.InitUI
#self.model = Model()
def main():
app = wx.App()
controller = Controller()
app.MainLoop()
if __name__ == '__main__':
main()
If you load the model from a method inside class View
then you will not need the PyPubSub module because you could just call wx.CallAfter(self.ShowLoadProgress, x)
回答3:
Just for fun and because I prefer the answer that kbr85 gave to my simplistic first answer, here's a Threaded variant with a gauge
in the statusbar
and a Busy cursor, although my screenshot program didn't pick it up.
There's a Stop
button and the statusbar
is removed once the load finishes.
Rather than use pubsub
, I've used a wxpython event
to communicate.
import wx
import time
from threading import Thread
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
load_status=["Model Loading","Model Loaded","Model Cancelled"]
class Model(Thread):
def __init__(self,parent):
Thread.__init__(self)
'''This thread simulates the loading of tensorflow'''
self.stopthread = 0
self.target = parent
self.start()
def run(self):
while not self.stopthread:
for i in range(20):
if self.stopthread:
break
time.sleep(0.5)
evt = progress_event(count=i, status=self.stopthread)
wx.PostEvent(self.target, evt)
if self.stopthread == 0:
self.stopthread = 1
evt = progress_event(count=i, status=self.stopthread)
wx.PostEvent(self.target, evt)
def terminate(self):
self.stopthread = 2
class View(wx.Frame):
def __init__(self, parent, title):
super(View, self).__init__(parent, title=title, size=(400, 400))
self.InitUI()
def InitUI(self):
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.fgs = wx.FlexGridSizer(6, 2, 10, 25)
id = wx.StaticText(self, label="ID:")
firstName = wx.StaticText(self, label="First name:")
lastName = wx.StaticText(self, label="Last name:")
self.idTc = wx.TextCtrl(self)
self.firstNameTc = wx.TextCtrl(self)
self.lastNameTc = wx.TextCtrl(self)
self.stop = wx.Button(self, -1, "Stop Load")
self.fgs.AddMany([id, (self.idTc, 1, wx.EXPAND),
firstName, (self.firstNameTc, 1, wx.EXPAND),
lastName, (self.lastNameTc, 1, wx.EXPAND),
(self.stop,1,wx.EXPAND)])
self.vbox.Add(self.fgs, proportion=1, flag=wx.ALL | wx.EXPAND,border=15)
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Stop button
self.Bind(wx.EVT_BUTTON, self.OnStop)
#Bind to Exit on frame close
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.SetSizer(self.vbox)
self.Layout()
self.statusbar = self.CreateStatusBar(2)
self.text = wx.StaticText(self.statusbar,-1,("No Model loaded"))
self.progress = wx.Gauge(self.statusbar, range=20)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.text, 0, wx.ALIGN_TOP|wx.ALL, 5)
sizer.Add(self.progress, 1, wx.ALIGN_TOP|wx.ALL, 5)
self.statusbar.SetSizer(sizer)
wx.BeginBusyCursor()
self.loadthread = Model(self)
def OnProgress(self, event):
self.text.SetLabel(load_status[event.status])
#self.progress.SetValue(event.count)
#or for indeterminate progress
self.progress.Pulse()
if event.status != 0:
self.statusbar.Hide()
wx.EndBusyCursor()
self.Layout()
def OnStop(self, event):
if self.loadthread.isAlive():
self.loadthread.terminate() # Shutdown the thread
self.loadthread.join() # Wait for it to finish
def OnExit(self, event):
if self.loadthread.isAlive():
self.loadthread.terminate() # Shutdown the thread
self.loadthread.join() # Wait for it to finish
self.Destroy()
class Controller:
def __init__(self):
self.view = View(None, title='Test')
self.view.Show()
def main():
app = wx.App()
controller = Controller()
app.MainLoop()
if __name__ == '__main__':
main()
来源:https://stackoverflow.com/questions/56532866/how-to-load-a-heavy-model-when-using-mvc-design-pattern