]>
Commit | Line | Data |
---|---|---|
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 | |
19 | import wx.lib.delayedresult as delayedresult | |
20 | ||
21 | ||
22 | class FrameSimpleDelayedBase(wx.Frame): | |
23 | def __init__(self, *args, **kwds): | |
24 | wx.Frame.__init__(self, *args, **kwds) | |
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) | |
32 | ||
33 | self.checkboxUseDelayed.SetValue(1) | |
34 | self.checkboxUseDelayed.Enable(False) | |
35 | self.buttonAbort.Enable(False) | |
36 | ||
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 | ||
48 | self.Bind(wx.EVT_BUTTON, self.handleGet, self.buttonGet) | |
49 | self.Bind(wx.EVT_BUTTON, self.handleAbort, self.buttonAbort) | |
50 | ||
51 | ||
52 | ||
53 | ||
54 | class FrameSimpleDelayed(FrameSimpleDelayedBase): | |
55 | """This demos simplistic use of delayedresult module.""" | |
56 | ||
57 | def __init__(self, *args, **kwargs): | |
58 | FrameSimpleDelayedBase.__init__(self, *args, **kwargs) | |
59 | self.jobID = 0 | |
60 | self.abortEvent = delayedresult.AbortEvent() | |
61 | self.Bind(wx.EVT_CLOSE, self.handleClose) | |
62 | ||
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(): | |
71 | self.log( "Exiting: Aborting job %s" % self.jobID ) | |
72 | self.abortEvent.set() | |
73 | self.Destroy() | |
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 | self.abortEvent.clear() | |
80 | self.jobID += 1 | |
81 | ||
82 | self.log( "Starting job %s in producer thread: GUI remains responsive" | |
83 | % self.jobID ) | |
84 | delayedresult.startWorker(self._resultConsumer, self._resultProducer, | |
85 | wargs=(self.jobID,self.abortEvent), jobID=self.jobID) | |
86 | ||
87 | ||
88 | def _resultProducer(self, jobID, abortEvent): | |
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 | |
93 | count = 0 | |
94 | while not abortEvent() and count < 50: | |
95 | time.sleep(0.1) | |
96 | count += 1 | |
97 | return jobID | |
98 | ||
99 | ||
100 | def handleAbort(self, event): | |
101 | """Abort the result computation.""" | |
102 | self.log( "Aborting result for job %s" % self.jobID ) | |
103 | self.buttonGet.Enable(True) | |
104 | self.buttonAbort.Enable(False) | |
105 | self.abortEvent.set() | |
106 | ||
107 | ||
108 | def _resultConsumer(self, delayedResult): | |
109 | jobID = delayedResult.getJobID() | |
110 | assert jobID == self.jobID | |
111 | try: | |
112 | result = delayedResult.get() | |
113 | except Exception, exc: | |
114 | self.log( "Result for job %s raised exception: %s" % (jobID, exc) ) | |
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) | |
124 | ||
125 | ||
126 | class FrameSimpleDirect(FrameSimpleDelayedBase): | |
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 | |
132 | FrameSimpleDelayedBase.__init__(self, *args, **kwargs) | |
133 | self.checkboxUseDelayed.SetValue(False) | |
134 | ||
135 | def setLog(self, log): | |
136 | self.log = log | |
137 | ||
138 | def handleGet(self, event): | |
139 | """Use delayedresult, this will compute result in separate | |
140 | thread, and will affect GUI response because a thread is not | |
141 | used.""" | |
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 ) | |
146 | result = self._resultProducer(self.jobID) | |
147 | self._resultConsumer( result ) | |
148 | ||
149 | def _resultProducer(self, jobID): | |
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 | ||
161 | def _resultConsumer(self, result): | |
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 |