]>
Commit | Line | Data |
---|---|---|
8bbd1bbf RD |
1 | """ |
2 | This demonstrates a simple use of delayedresult: get/compute | |
3 | something that takes a long time, without hanging the GUI while this | |
4 | is taking place. | |
5 | ||
6 | The top button runs a small GUI that uses wx.lib.delayedresult.startWorker | |
7 | to wrap a long-running function into a separate thread. Just click | |
8 | Get, and move the slider, and click Get and Abort a few times, and | |
9 | observe that GUI responds. The key functions to look for in the code | |
10 | are startWorker() and __handleResult(). | |
11 | ||
12 | The second button runs the same GUI, but without delayedresult. Click | |
13 | Get: now the get/compute is taking place in main thread, so the GUI | |
14 | does not respond to user actions until worker function returns, it's | |
15 | not even possible to Abort. | |
16 | """ | |
17 | ||
18 | import wx | |
24648f55 | 19 | import wx.lib.delayedresult as delayedresult |
8bbd1bbf | 20 | |
24648f55 RD |
21 | |
22 | class FrameSimpleDelayedBase(wx.Frame): | |
8bbd1bbf | 23 | def __init__(self, *args, **kwds): |
8bbd1bbf | 24 | wx.Frame.__init__(self, *args, **kwds) |
24648f55 RD |
25 | pnl = wx.Panel(self) |
26 | self.checkboxUseDelayed = wx.CheckBox(pnl, -1, "Using delayedresult") | |
27 | self.buttonGet = wx.Button(pnl, -1, "Get") | |
28 | self.buttonAbort = wx.Button(pnl, -1, "Abort") | |
29 | self.slider = wx.Slider(pnl, -1, 0, 0, 10, size=(100,-1), | |
30 | style=wx.SL_HORIZONTAL|wx.SL_AUTOTICKS) | |
31 | self.textCtrlResult = wx.TextCtrl(pnl, -1, "", style=wx.TE_READONLY) | |
8bbd1bbf | 32 | |
24648f55 RD |
33 | self.checkboxUseDelayed.SetValue(1) |
34 | self.checkboxUseDelayed.Enable(False) | |
35 | self.buttonAbort.Enable(False) | |
8bbd1bbf | 36 | |
24648f55 RD |
37 | vsizer = wx.BoxSizer(wx.VERTICAL) |
38 | hsizer = wx.BoxSizer(wx.HORIZONTAL) | |
39 | vsizer.Add(self.checkboxUseDelayed, 0, wx.ALL, 10) | |
40 | hsizer.Add(self.buttonGet, 0, wx.ALL, 5) | |
41 | hsizer.Add(self.buttonAbort, 0, wx.ALL, 5) | |
42 | hsizer.Add(self.slider, 0, wx.ALL, 5) | |
43 | hsizer.Add(self.textCtrlResult, 0, wx.ALL, 5) | |
44 | vsizer.Add(hsizer, 0, wx.ALL, 5) | |
45 | pnl.SetSizer(vsizer) | |
46 | vsizer.SetSizeHints(self) | |
47 | ||
8bbd1bbf RD |
48 | self.Bind(wx.EVT_BUTTON, self.handleGet, self.buttonGet) |
49 | self.Bind(wx.EVT_BUTTON, self.handleAbort, self.buttonAbort) | |
8bbd1bbf | 50 | |
24648f55 RD |
51 | |
52 | ||
53 | ||
54 | class FrameSimpleDelayed(FrameSimpleDelayedBase): | |
8bbd1bbf RD |
55 | """This demos simplistic use of delayedresult module.""" |
56 | ||
57 | def __init__(self, *args, **kwargs): | |
24648f55 | 58 | FrameSimpleDelayedBase.__init__(self, *args, **kwargs) |
8bbd1bbf | 59 | self.jobID = 1 |
8bbd1bbf | 60 | self.Bind(wx.EVT_CLOSE, self.handleClose) |
24648f55 | 61 | |
8bbd1bbf RD |
62 | def setLog(self, log): |
63 | self.log = log | |
64 | ||
65 | def handleClose(self, event): | |
66 | """Only needed because in demo, closing the window does not kill the | |
67 | app, so worker thread continues and sends result to dead frame; normally | |
68 | your app would exit so this would not happen.""" | |
69 | if self.buttonAbort.IsEnabled(): | |
70 | self.Hide() | |
24648f55 RD |
71 | wx.FutureCall(5000, self.Destroy) |
72 | else: | |
73 | self.Destroy() | |
8bbd1bbf RD |
74 | |
75 | def handleGet(self, event): | |
76 | """Compute result in separate thread, doesn't affect GUI response.""" | |
77 | self.buttonGet.Enable(False) | |
78 | self.buttonAbort.Enable(True) | |
79 | ||
24648f55 RD |
80 | self.log( "Starting job %s in producer thread: GUI remains responsive" |
81 | % self.jobID ) | |
82 | delayedresult.startWorker(self._resultConsumer, self._resultProducer, | |
83 | wargs=(self.jobID,), jobID=self.jobID) | |
84 | ||
8bbd1bbf | 85 | |
24648f55 | 86 | def _resultProducer(self, jobID): |
8bbd1bbf RD |
87 | """Pretend to be a complex worker function or something that takes |
88 | long time to run due to network access etc. GUI will freeze if this | |
89 | method is not called in separate thread.""" | |
90 | import time | |
91 | time.sleep(5) | |
92 | return jobID | |
93 | ||
24648f55 | 94 | |
8bbd1bbf RD |
95 | def handleAbort(self, event): |
96 | """Abort actually just means 'ignore the result when it gets to | |
97 | handler, it is no longer relevant'. We just increase the job ID, | |
98 | this will let handler know that the result has been cancelled.""" | |
99 | self.log( "Aborting result for job %s" % self.jobID ) | |
100 | self.buttonGet.Enable(True) | |
101 | self.buttonAbort.Enable(False) | |
102 | self.jobID += 1 | |
24648f55 | 103 | |
8bbd1bbf | 104 | |
24648f55 | 105 | def _resultConsumer(self, delayedResult): |
8bbd1bbf RD |
106 | # See if we still want the result for last job started |
107 | jobID = delayedResult.getJobID() | |
108 | if jobID != self.jobID: | |
109 | self.log( "Got obsolete result for job %s, ignored" % jobID ) | |
110 | return | |
111 | ||
112 | # we do, get result: | |
113 | try: | |
114 | result = delayedResult.get() | |
115 | except Exception, exc: | |
116 | self.log( "Result for job %s raised exception: %s" % (jobID, exc) ) | |
117 | self.jobID += 1 | |
118 | return | |
119 | ||
120 | # output result | |
121 | self.log( "Got result for job %s: %s" % (jobID, result) ) | |
122 | self.textCtrlResult.SetValue(str(result)) | |
123 | ||
124 | # get ready for next job: | |
125 | self.buttonGet.Enable(True) | |
126 | self.buttonAbort.Enable(False) | |
127 | self.jobID += 1 | |
128 | ||
129 | ||
24648f55 | 130 | class FrameSimpleDirect(FrameSimpleDelayedBase): |
8bbd1bbf RD |
131 | """This does not use delayedresult so the GUI will freeze while |
132 | the GET is taking place.""" | |
133 | ||
134 | def __init__(self, *args, **kwargs): | |
135 | self.jobID = 1 | |
24648f55 | 136 | FrameSimpleDelayedBase.__init__(self, *args, **kwargs) |
8bbd1bbf RD |
137 | self.checkboxUseDelayed.SetValue(False) |
138 | ||
139 | def setLog(self, log): | |
140 | self.log = log | |
141 | ||
142 | def handleGet(self, event): | |
24648f55 RD |
143 | """Use delayedresult, this will compute result in separate |
144 | thread, and will affect GUI response because a thread is not | |
145 | used.""" | |
8bbd1bbf RD |
146 | self.buttonGet.Enable(False) |
147 | self.buttonAbort.Enable(True) | |
148 | ||
149 | self.log( "Doing job %s without delayedresult (same as GUI thread): GUI hangs (for a while)" % self.jobID ) | |
24648f55 RD |
150 | result = self._resultProducer(self.jobID) |
151 | self._resultConsumer( result ) | |
8bbd1bbf | 152 | |
24648f55 | 153 | def _resultProducer(self, jobID): |
8bbd1bbf RD |
154 | """Pretend to be a complex worker function or something that takes |
155 | long time to run due to network access etc. GUI will freeze if this | |
156 | method is not called in separate thread.""" | |
157 | import time | |
158 | time.sleep(5) | |
159 | return jobID | |
160 | ||
161 | def handleAbort(self, event): | |
162 | """can never be called""" | |
163 | pass | |
164 | ||
24648f55 | 165 | def _resultConsumer(self, result): |
8bbd1bbf RD |
166 | # output result |
167 | self.log( "Got result for job %s: %s" % (self.jobID, result) ) | |
168 | self.textCtrlResult.SetValue(str(result)) | |
169 | ||
170 | # get ready for next job: | |
171 | self.buttonGet.Enable(True) | |
172 | self.buttonAbort.Enable(False) | |
173 | self.jobID += 1 | |
174 | ||
175 | ||
176 | #--------------------------------------------------------------------------- | |
177 | #--------------------------------------------------------------------------- | |
178 | ||
179 | class TestPanel(wx.Panel): | |
180 | def __init__(self, parent, log): | |
181 | self.log = log | |
182 | wx.Panel.__init__(self, parent, -1) | |
183 | ||
184 | vsizer = wx.BoxSizer(wx.VERTICAL) | |
185 | b = wx.Button(self, -1, "Long-running function in separate thread") | |
186 | vsizer.Add(b, 0, wx.ALL, 5) | |
187 | self.Bind(wx.EVT_BUTTON, self.OnButton1, b) | |
188 | ||
189 | b = wx.Button(self, -1, "Long-running function in GUI thread") | |
190 | vsizer.Add(b, 0, wx.ALL, 5) | |
191 | self.Bind(wx.EVT_BUTTON, self.OnButton2, b) | |
192 | ||
193 | bdr = wx.BoxSizer() | |
194 | bdr.Add(vsizer, 0, wx.ALL, 50) | |
195 | self.SetSizer(bdr) | |
196 | self.Layout() | |
197 | ||
198 | def OnButton1(self, evt): | |
199 | frame = FrameSimpleDelayed(self, title="Long-running function in separate thread") | |
200 | frame.setLog(self.log.WriteText) | |
201 | frame.Show() | |
202 | ||
203 | def OnButton2(self, evt): | |
204 | frame = FrameSimpleDirect(self, title="Long-running function in GUI thread") | |
205 | frame.setLog(self.log.WriteText) | |
206 | frame.Show() | |
207 | ||
208 | ||
209 | #--------------------------------------------------------------------------- | |
210 | ||
211 | ||
212 | def runTest(frame, nb, log): | |
213 | win = TestPanel(nb, log) | |
214 | return win | |
215 | ||
216 | ||
217 | #--------------------------------------------------------------------------- | |
218 | ||
219 | ||
220 | overview = __doc__ | |
221 | ||
222 | ||
223 | if __name__ == '__main__': | |
224 | import sys,os | |
225 | import run | |
226 | run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:]) | |
227 | ||
228 |