wxPython使用delayedresult进行耗时处理
delayedresult使用背景介绍
在进行wxPython GUI画面编程时,如直接在画面主线程进行大量耗时计算处理,就会造成画面假死,不能响应用户输入。
使用wxPython的delayedresult模块,可轻松解决该问题,甚至都不需要了解相关线程处理机制,即可方便的把耗时处理放到单独的线程中,处理结束后把结果返回GUI画面主线程,并调用预先定义的相关处理,进行画面更新等。
为了演示delayedresult的使用情况,先新建一TestWindow框架,doSomeThing()是我们模拟进行大量耗时处理的函数。
1 import wx 2 from wx.lib.delayedresult import startWorker 3 import threading 4 5 class TestWindow(wx.Frame): 6 def __init__(self, title='Test Window'): 7 self.app = wx.App(False) 8 wx.Frame.__init__(self, None, -1, title) 9 panel = wx.Panel(self) 10 self.btnBegin = wx.Button(panel, -1, label='Begin') 11 self.Bind(wx.EVT_BUTTON, self.handleButton, self.btnBegin) 12 self.txtCtrl = wx.TextCtrl(panel, style=wx.TE_READONLY, size=(300, -1)) 13 vsizer = wx.BoxSizer(wx.VERTICAL) 14 vsizer.Add(self.btnBegin, 0, wx.ALL, 5) 15 vsizer.Add(self.txtCtrl, 0, wx.ALL, 5) 16 panel.SetSizer(vsizer) 17 vsizer.SetSizeHints(self) 18 self.Show() 19 20 #处理Begin按钮事件 21 def handleButton(self, event): 22 self.workFunction() 23 24 #开始执行耗时处理,有继承类实现 25 def workFunction(self, *args, **kwargs): 26 print'In workFunction(), Thread=', threading.currentThread().name 27 print '\t*args:', args 28 print '\t**kwargs:', kwargs 29 30 self.btnBegin.Enable(False) 31 32 #耗时处理处理完成后,调用该函数执行画面更新显示,由继承类实现 33 def consumer(self, delayedResult, *args, **kwargs): 34 print 'In consumer(), Thread=', threading.currentThread().name 35 print '\tdelayedResult:', delayedResult 36 print '\t*args:', args 37 print '\t**kwargs:', kwargs 38 39 self.btnBegin.Enable(True) 40 41 #模拟进行耗时处理并返回处理结果,给继承类使用 42 def doSomeThing(self, *args, **kwargs): 43 print'In doSomeThing(), Thread=', threading.currentThread().name 44 print '\t*args:', args 45 print '\t**kwargs:', kwargs 46 47 count = 0 48 while count < 10**8: 49 count += 1 50 51 return count
运行界面:
先看看如在GUI画面主线程进行doSomeThing()处理的
1 class DoSomeThingInGUI(TestWindow): 2 def __init__(self): 3 TestWindow.__init__(self, 'Do Something In GUI Thread') 4 self.app.MainLoop() 5 6 #执行doSomeThing(),并在执行完成后,主动调用consumer()更新画面显示 7 def workFunction(self, *args, **kwargs): 8 TestWindow.workFunction(self, args, kwargs) 9 var = self.doSomeThing() 10 self.consumer(var) 11 12 def consumer(self, delayedResult, *args, **kwargs): 13 TestWindow.consumer(self, delayedResult, args, kwargs) 14 self.txtCtrl.SetValue(str(delayedResult)) 15 16 if __name__ == '__main__': 17 win = DoSomeThingInGUI()
执行后,点Begin开始后,直到处理完成画面窗口无法移动。
由以下log输出可以知道,处理都是在GUI画面主线程中完成的。
In workFunction(), Thread= MainThread
*args: ((), {})
**kwargs: {}
In doSomeThing(), Thread= MainThread
*args: ()
**kwargs: {}
In consumer(), Thread= MainThread
delayedResult: 100000000
*args: ((), {})
**kwargs: {}
使用wx.lib.delayedresult后的处理情况:
1 class DoSomeThingInSeperateThread(TestWindow): 2 def __init__(self): 3 TestWindow.__init__(self, 'Do Something In Seperate Thread') 4 self.jobId = 100 5 self.app.MainLoop() 6 7 #调用wx.lib.delayedresult.startWorker,把函数处理放到单独的线程中去完成。 8 #完成后会自动调用consumer进行画面更新处理 9 #startWorker函数的各参数接下来分析 10 def workFunction(self, *args, **kwargs): 11 TestWindow.workFunction(self, args, kwargs) 12 startWorker(self.consumer, self.doSomeThing, jobID=self.jobId) 13 14 #第一参数要为DelayedResult类型,即包含处理结果或异常信息,调用get接口取得。 15 def consumer(self, delayedResult, *args, **kwargs): 16 TestWindow.consumer(self, delayedResult, args, kwargs) 17 assert(self.jobId == delayedResult.getJobID()) 18 try: 19 var = delayedResult.get() 20 except Exception, e: 21 print 'Result for job %s raised exception:%s' %(delayedResult.getJobID, e) 22 23 self.txtCtrl.SetValue(str(var)) 24 25 if __name__ == '__main__': 26 win = DoSomeThingInSeperateThread()
执行后,点Begin开始后,窗口移动不受影响,也就是说处理是异步的。
由以下log输出可以知道,耗时处理doSomeThing是在独立线程运行的。
处理完成后的画面更新处理consumer还是在GUI画面主线程完成的。
In workFunction(), Thread= MainThread
*args: ((), {})
**kwargs: {}
In doSomeThing(), Thread= 100
*args: ()
**kwargs: {}
In consumer(), Thread= MainThread
delayedResult: <wx.lib.delayedresult.DelayedResult instance at 0x02FFE828>
*args: ((), {})
**kwargs: {}
startWorker函数介绍
该函数创建独立线程(Producer线程)执行workerFn(*wargs, **wkwargs),并将其结果发送给运行在Main线程的consumer(*cargs, ckwargs)。
jobID即为创建的Producer线程的名字,也可在consumer中判断是那个Producer线程。
详细可参考 help(wx.lib.delayedresult)
startWorker(consumer, workerFn, cargs=(), ckwargs={}, wargs=(), wkwargs={}, jobID=None, group=None, daemon=False, sendReturn=True, senderArg=None)
注意事项:
1)sendResurn为True的情况下,workFn执行结束后才执行consumer处理。
2)consumer第一参数为DelayedResult,可使用其结果get()取得workFn中返回的结果,getJobID()用于返回参数jobID或者None(jobID未指定时)
3)cargs, ckwargs传递给consumer;wargs, wkargs传递给workerFn