]>
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) |
45c0ea45 RD |
59 | self.jobID = 0 |
60 | self.abortEvent = delayedresult.AbortEvent() | |
8bbd1bbf | 61 | self.Bind(wx.EVT_CLOSE, self.handleClose) |
24648f55 | 62 | |
8bbd1bbf RD |
63 | def setLog(self, log): |
64 | self.log = log | |
65 | ||
66 | def handleClose(self, event): | |
67 | """Only needed because in demo, closing the window does not kill the | |
68 | app, so worker thread continues and sends result to dead frame; normally | |
69 | your app would exit so this would not happen.""" | |
70 | if self.buttonAbort.IsEnabled(): | |
45c0ea45 RD |
71 | self.log( "Exiting: Aborting job %s" % self.jobID ) |
72 | self.abortEvent.set() | |
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) | |
45c0ea45 RD |
79 | self.abortEvent.clear() |
80 | self.jobID += 1 | |
81 | ||
24648f55 RD |
82 | self.log( "Starting job %s in producer thread: GUI remains responsive" |
83 | % self.jobID ) | |
84 | delayedresult.startWorker(self._resultConsumer, self._resultProducer, | |
45c0ea45 | 85 | wargs=(self.jobID,self.abortEvent), jobID=self.jobID) |
24648f55 | 86 | |
8bbd1bbf | 87 | |
45c0ea45 | 88 | def _resultProducer(self, jobID, abortEvent): |
8bbd1bbf RD |
89 | """Pretend to be a complex worker function or something that takes |
90 | long time to run due to network access etc. GUI will freeze if this | |
91 | method is not called in separate thread.""" | |
92 | import time | |
45c0ea45 RD |
93 | count = 0 |
94 | while not abortEvent() and count < 50: | |
95 | time.sleep(0.1) | |
96 | count += 1 | |
8bbd1bbf RD |
97 | return jobID |
98 | ||
24648f55 | 99 | |
8bbd1bbf | 100 | def handleAbort(self, event): |
45c0ea45 | 101 | """Abort the result computation.""" |
8bbd1bbf RD |
102 | self.log( "Aborting result for job %s" % self.jobID ) |
103 | self.buttonGet.Enable(True) | |
104 | self.buttonAbort.Enable(False) | |
45c0ea45 | 105 | self.abortEvent.set() |
24648f55 | 106 | |
8bbd1bbf | 107 | |
24648f55 | 108 | def _resultConsumer(self, delayedResult): |
8bbd1bbf | 109 | jobID = delayedResult.getJobID() |
45c0ea45 | 110 | assert jobID == self.jobID |
8bbd1bbf RD |
111 | try: |
112 | result = delayedResult.get() | |
113 | except Exception, exc: | |
114 | self.log( "Result for job %s raised exception: %s" % (jobID, exc) ) | |
8bbd1bbf RD |
115 | return |
116 | ||
117 | # output result | |
118 | self.log( "Got result for job %s: %s" % (jobID, result) ) | |
119 | self.textCtrlResult.SetValue(str(result)) | |
120 | ||
121 | # get ready for next job: | |
122 | self.buttonGet.Enable(True) | |
123 | self.buttonAbort.Enable(False) | |
8bbd1bbf RD |
124 | |
125 | ||
24648f55 | 126 | class FrameSimpleDirect(FrameSimpleDelayedBase): |
8bbd1bbf RD |
127 | """This does not use delayedresult so the GUI will freeze while |
128 | the GET is taking place.""" | |
129 | ||
130 | def __init__(self, *args, **kwargs): | |
131 | self.jobID = 1 | |
24648f55 | 132 | FrameSimpleDelayedBase.__init__(self, *args, **kwargs) |
8bbd1bbf RD |
133 | self.checkboxUseDelayed.SetValue(False) |
134 | ||
135 | def setLog(self, log): | |
136 | self.log = log | |
137 | ||
138 | def handleGet(self, event): | |
24648f55 RD |
139 | """Use delayedresult, this will compute result in separate |
140 | thread, and will affect GUI response because a thread is not | |
141 | used.""" | |
8bbd1bbf RD |
142 | self.buttonGet.Enable(False) |
143 | self.buttonAbort.Enable(True) | |
144 | ||
145 | self.log( "Doing job %s without delayedresult (same as GUI thread): GUI hangs (for a while)" % self.jobID ) | |
24648f55 RD |
146 | result = self._resultProducer(self.jobID) |
147 | self._resultConsumer( result ) | |
8bbd1bbf | 148 | |
24648f55 | 149 | def _resultProducer(self, jobID): |
8bbd1bbf RD |
150 | """Pretend to be a complex worker function or something that takes |
151 | long time to run due to network access etc. GUI will freeze if this | |
152 | method is not called in separate thread.""" | |
153 | import time | |
154 | time.sleep(5) | |
155 | return jobID | |
156 | ||
157 | def handleAbort(self, event): | |
158 | """can never be called""" | |
159 | pass | |
160 | ||
24648f55 | 161 | def _resultConsumer(self, result): |
8bbd1bbf RD |
162 | # output result |
163 | self.log( "Got result for job %s: %s" % (self.jobID, result) ) | |
164 | self.textCtrlResult.SetValue(str(result)) | |
165 | ||
166 | # get ready for next job: | |
167 | self.buttonGet.Enable(True) | |
168 | self.buttonAbort.Enable(False) | |
169 | self.jobID += 1 | |
170 | ||
171 | ||
172 | #--------------------------------------------------------------------------- | |
173 | #--------------------------------------------------------------------------- | |
174 | ||
175 | class TestPanel(wx.Panel): | |
176 | def __init__(self, parent, log): | |
177 | self.log = log | |
178 | wx.Panel.__init__(self, parent, -1) | |
179 | ||
180 | vsizer = wx.BoxSizer(wx.VERTICAL) | |
181 | b = wx.Button(self, -1, "Long-running function in separate thread") | |
182 | vsizer.Add(b, 0, wx.ALL, 5) | |
183 | self.Bind(wx.EVT_BUTTON, self.OnButton1, b) | |
184 | ||
185 | b = wx.Button(self, -1, "Long-running function in GUI thread") | |
186 | vsizer.Add(b, 0, wx.ALL, 5) | |
187 | self.Bind(wx.EVT_BUTTON, self.OnButton2, b) | |
188 | ||
189 | bdr = wx.BoxSizer() | |
190 | bdr.Add(vsizer, 0, wx.ALL, 50) | |
191 | self.SetSizer(bdr) | |
192 | self.Layout() | |
193 | ||
194 | def OnButton1(self, evt): | |
195 | frame = FrameSimpleDelayed(self, title="Long-running function in separate thread") | |
196 | frame.setLog(self.log.WriteText) | |
197 | frame.Show() | |
198 | ||
199 | def OnButton2(self, evt): | |
200 | frame = FrameSimpleDirect(self, title="Long-running function in GUI thread") | |
201 | frame.setLog(self.log.WriteText) | |
202 | frame.Show() | |
203 | ||
204 | ||
205 | #--------------------------------------------------------------------------- | |
206 | ||
207 | ||
208 | def runTest(frame, nb, log): | |
209 | win = TestPanel(nb, log) | |
210 | return win | |
211 | ||
212 | ||
213 | #--------------------------------------------------------------------------- | |
214 | ||
215 | ||
216 | overview = __doc__ | |
217 | ||
218 | ||
219 | if __name__ == '__main__': | |
220 | import sys,os | |
221 | import run | |
222 | run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:]) | |
223 | ||
224 |