git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@41354 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
		
			
				
	
	
		
			225 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			225 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""
 | 
						|
This demonstrates a simple use of delayedresult: get/compute 
 | 
						|
something that takes a long time, without hanging the GUI while this
 | 
						|
is taking place. 
 | 
						|
 | 
						|
The top button runs a small GUI that uses wx.lib.delayedresult.startWorker
 | 
						|
to wrap a long-running function into a separate thread. Just click
 | 
						|
Get, and move the slider, and click Get and Abort a few times, and
 | 
						|
observe that GUI responds. The key functions to look for in the code
 | 
						|
are startWorker() and __handleResult().
 | 
						|
 | 
						|
The second button runs the same GUI, but without delayedresult.  Click
 | 
						|
Get: now the get/compute is taking place in main thread, so the GUI
 | 
						|
does not respond to user actions until worker function returns, it's
 | 
						|
not even possible to Abort.
 | 
						|
"""
 | 
						|
 | 
						|
import wx
 | 
						|
import wx.lib.delayedresult as delayedresult
 | 
						|
 | 
						|
 | 
						|
class FrameSimpleDelayedBase(wx.Frame):
 | 
						|
    def __init__(self, *args, **kwds):
 | 
						|
        wx.Frame.__init__(self, *args, **kwds)
 | 
						|
        pnl = wx.Panel(self)
 | 
						|
        self.checkboxUseDelayed = wx.CheckBox(pnl, -1, "Using delayedresult")
 | 
						|
        self.buttonGet = wx.Button(pnl, -1, "Get")
 | 
						|
        self.buttonAbort = wx.Button(pnl, -1, "Abort")
 | 
						|
        self.slider = wx.Slider(pnl, -1, 0, 0, 10, size=(100,-1),
 | 
						|
                                style=wx.SL_HORIZONTAL|wx.SL_AUTOTICKS)
 | 
						|
        self.textCtrlResult = wx.TextCtrl(pnl, -1, "", style=wx.TE_READONLY)
 | 
						|
 | 
						|
        self.checkboxUseDelayed.SetValue(1)
 | 
						|
        self.checkboxUseDelayed.Enable(False)
 | 
						|
        self.buttonAbort.Enable(False)
 | 
						|
 | 
						|
        vsizer = wx.BoxSizer(wx.VERTICAL)
 | 
						|
        hsizer = wx.BoxSizer(wx.HORIZONTAL)
 | 
						|
        vsizer.Add(self.checkboxUseDelayed, 0, wx.ALL, 10)
 | 
						|
        hsizer.Add(self.buttonGet, 0, wx.ALL, 5)
 | 
						|
        hsizer.Add(self.buttonAbort, 0, wx.ALL, 5)
 | 
						|
        hsizer.Add(self.slider, 0, wx.ALL, 5)
 | 
						|
        hsizer.Add(self.textCtrlResult, 0, wx.ALL, 5)
 | 
						|
        vsizer.Add(hsizer, 0, wx.ALL, 5)
 | 
						|
        pnl.SetSizer(vsizer)
 | 
						|
        vsizer.SetSizeHints(self)
 | 
						|
        
 | 
						|
        self.Bind(wx.EVT_BUTTON, self.handleGet, self.buttonGet)
 | 
						|
        self.Bind(wx.EVT_BUTTON, self.handleAbort, self.buttonAbort)
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
class FrameSimpleDelayed(FrameSimpleDelayedBase):
 | 
						|
    """This demos simplistic use of delayedresult module."""
 | 
						|
    
 | 
						|
    def __init__(self, *args, **kwargs):
 | 
						|
        FrameSimpleDelayedBase.__init__(self, *args, **kwargs)
 | 
						|
        self.jobID = 0
 | 
						|
        self.abortEvent = delayedresult.AbortEvent()
 | 
						|
        self.Bind(wx.EVT_CLOSE, self.handleClose)
 | 
						|
 | 
						|
    def setLog(self, log):
 | 
						|
        self.log = log
 | 
						|
 | 
						|
    def handleClose(self, event):
 | 
						|
        """Only needed because in demo, closing the window does not kill the 
 | 
						|
        app, so worker thread continues and sends result to dead frame; normally
 | 
						|
        your app would exit so this would not happen."""
 | 
						|
        if self.buttonAbort.IsEnabled():
 | 
						|
            self.log( "Exiting: Aborting job %s" % self.jobID )
 | 
						|
            self.abortEvent.set()
 | 
						|
        self.Destroy()
 | 
						|
            
 | 
						|
    def handleGet(self, event): 
 | 
						|
        """Compute result in separate thread, doesn't affect GUI response."""
 | 
						|
        self.buttonGet.Enable(False)
 | 
						|
        self.buttonAbort.Enable(True)
 | 
						|
        self.abortEvent.clear()
 | 
						|
        self.jobID += 1
 | 
						|
        
 | 
						|
        self.log( "Starting job %s in producer thread: GUI remains responsive"
 | 
						|
                  % self.jobID )
 | 
						|
        delayedresult.startWorker(self._resultConsumer, self._resultProducer, 
 | 
						|
                                  wargs=(self.jobID,self.abortEvent), jobID=self.jobID)
 | 
						|
 | 
						|
                        
 | 
						|
    def _resultProducer(self, jobID, abortEvent):
 | 
						|
        """Pretend to be a complex worker function or something that takes 
 | 
						|
        long time to run due to network access etc. GUI will freeze if this 
 | 
						|
        method is not called in separate thread."""
 | 
						|
        import time
 | 
						|
        count = 0
 | 
						|
        while not abortEvent() and count < 50:
 | 
						|
            time.sleep(0.1)
 | 
						|
            count += 1
 | 
						|
        return jobID
 | 
						|
 | 
						|
 | 
						|
    def handleAbort(self, event): 
 | 
						|
        """Abort the result computation."""
 | 
						|
        self.log( "Aborting result for job %s" % self.jobID )
 | 
						|
        self.buttonGet.Enable(True)
 | 
						|
        self.buttonAbort.Enable(False)
 | 
						|
        self.abortEvent.set()
 | 
						|
 | 
						|
        
 | 
						|
    def _resultConsumer(self, delayedResult):
 | 
						|
        jobID = delayedResult.getJobID()
 | 
						|
        assert jobID == self.jobID
 | 
						|
        try:
 | 
						|
            result = delayedResult.get()
 | 
						|
        except Exception, exc:
 | 
						|
            self.log( "Result for job %s raised exception: %s" % (jobID, exc) )
 | 
						|
            return
 | 
						|
        
 | 
						|
        # output result
 | 
						|
        self.log( "Got result for job %s: %s" % (jobID, result) )
 | 
						|
        self.textCtrlResult.SetValue(str(result))
 | 
						|
        
 | 
						|
        # get ready for next job:
 | 
						|
        self.buttonGet.Enable(True)
 | 
						|
        self.buttonAbort.Enable(False)
 | 
						|
 | 
						|
 | 
						|
class FrameSimpleDirect(FrameSimpleDelayedBase):
 | 
						|
    """This does not use delayedresult so the GUI will freeze while
 | 
						|
    the GET is taking place."""
 | 
						|
    
 | 
						|
    def __init__(self, *args, **kwargs):
 | 
						|
        self.jobID = 1
 | 
						|
        FrameSimpleDelayedBase.__init__(self, *args, **kwargs)
 | 
						|
        self.checkboxUseDelayed.SetValue(False)
 | 
						|
                
 | 
						|
    def setLog(self, log):
 | 
						|
        self.log = log
 | 
						|
        
 | 
						|
    def handleGet(self, event): 
 | 
						|
        """Use delayedresult, this will compute result in separate
 | 
						|
        thread, and will affect GUI response because a thread is not
 | 
						|
        used."""
 | 
						|
        self.buttonGet.Enable(False)
 | 
						|
        self.buttonAbort.Enable(True)
 | 
						|
 | 
						|
        self.log( "Doing job %s without delayedresult (same as GUI thread): GUI hangs (for a while)" % self.jobID )
 | 
						|
        result = self._resultProducer(self.jobID)
 | 
						|
        self._resultConsumer( result )
 | 
						|
 | 
						|
    def _resultProducer(self, jobID):
 | 
						|
        """Pretend to be a complex worker function or something that takes 
 | 
						|
        long time to run due to network access etc. GUI will freeze if this 
 | 
						|
        method is not called in separate thread."""
 | 
						|
        import time
 | 
						|
        time.sleep(5)
 | 
						|
        return jobID
 | 
						|
 | 
						|
    def handleAbort(self, event):
 | 
						|
        """can never be called"""
 | 
						|
        pass
 | 
						|
        
 | 
						|
    def _resultConsumer(self, result):
 | 
						|
        # output result
 | 
						|
        self.log( "Got result for job %s: %s" % (self.jobID, result) )
 | 
						|
        self.textCtrlResult.SetValue(str(result))
 | 
						|
        
 | 
						|
        # get ready for next job:
 | 
						|
        self.buttonGet.Enable(True)
 | 
						|
        self.buttonAbort.Enable(False)
 | 
						|
        self.jobID += 1
 | 
						|
 | 
						|
 | 
						|
#---------------------------------------------------------------------------
 | 
						|
#---------------------------------------------------------------------------
 | 
						|
 | 
						|
class TestPanel(wx.Panel):
 | 
						|
    def __init__(self, parent, log):
 | 
						|
        self.log = log
 | 
						|
        wx.Panel.__init__(self, parent, -1)
 | 
						|
 | 
						|
        vsizer = wx.BoxSizer(wx.VERTICAL)
 | 
						|
        b = wx.Button(self, -1, "Long-running function in separate thread")
 | 
						|
        vsizer.Add(b, 0, wx.ALL, 5)
 | 
						|
        self.Bind(wx.EVT_BUTTON, self.OnButton1, b)
 | 
						|
 | 
						|
        b = wx.Button(self, -1, "Long-running function in GUI thread")
 | 
						|
        vsizer.Add(b, 0, wx.ALL, 5)
 | 
						|
        self.Bind(wx.EVT_BUTTON, self.OnButton2, b)
 | 
						|
 | 
						|
        bdr = wx.BoxSizer()
 | 
						|
        bdr.Add(vsizer, 0, wx.ALL, 50)
 | 
						|
        self.SetSizer(bdr)
 | 
						|
        self.Layout()
 | 
						|
 | 
						|
    def OnButton1(self, evt):
 | 
						|
        frame = FrameSimpleDelayed(self, title="Long-running function in separate thread")
 | 
						|
        frame.setLog(self.log.WriteText)
 | 
						|
        frame.Show()
 | 
						|
 | 
						|
    def OnButton2(self, evt):
 | 
						|
        frame = FrameSimpleDirect(self, title="Long-running function in GUI thread")
 | 
						|
        frame.setLog(self.log.WriteText)
 | 
						|
        frame.Show()
 | 
						|
    
 | 
						|
 | 
						|
#---------------------------------------------------------------------------
 | 
						|
 | 
						|
 | 
						|
def runTest(frame, nb, log):
 | 
						|
    win = TestPanel(nb, log)
 | 
						|
    return win
 | 
						|
 | 
						|
 | 
						|
#---------------------------------------------------------------------------
 | 
						|
 | 
						|
 | 
						|
overview = __doc__
 | 
						|
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
   import sys,os
 | 
						|
   import run
 | 
						|
   run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
 | 
						|
 | 
						|
 |