wxPythonで長時間実行されるタスク中にGUIの応答性を維持する方法
wxpythonのwx.CallLater - メグタンの何でもブログ の続き 画面のボタンはStartとAbortの2つになってます。
長時間実行されるタスク中にGUIの応答性を維持する
CPUを使い続ける計算等でメインスレッドを占有すると、ウインドウが固まってしまい操作性が阻害される。
回避するには次のような方法がある。
wx.Yield()を使用する方法
wx.Yield()によって一時的に関数処理を中断しメインスレッドに他の処理をさせる。
計算ループ内で実行される頻度によって応答性は変わる。
画面の処理が行われるので、計算が終わるまで起動ボタンを無効化する。
def m_buttonStartOnButtonClick( self, event ): self.m_buttonStart.Enable(False) self.guiloop() self.m_buttonStart.Enable(True)
def guiloop(self): for i in range(0,100,1): time.sleep(0.1) # ここで時間の掛かる処理 self.m_gauge1.SetValue(i) wx.Yield()
wx.lib.delayedresultを使用する方法
import wx.lib.delayedresult as delayedresult
中断用にAbortEventを作っておく
self.abortEvent = delayedresult.AbortEvent()
self.jobID = 0
Startボタンを押した時の処理
Startボタンを無効化してAbortボタンを有効にする
def m_buttonStartOnButtonClick( self, event ): self.m_buttonStart.Enable(False) self.m_buttonAbort.Enable(True) self.abortEvent.clear() self.jobID += 1 delayedresult.startWorker(self.guiloop_finish, self.guiloop, wargs=(self.jobID,self.abortEvent), jobID=self.jobID)
Abortボタンを押した時の処理
AbortEventをセットする
Startボタンを有効化してAbortボタンを無効化する
def m_buttonAbortOnButtonClick( self, event ): self.abortEvent.set() self.m_buttonStart.Enable(True) self.m_buttonAbort.Enable(False)
別スレッドでの重たい処理
途中経過で画面を操作したい時にはCallAfterでメインスレッドに委託
def guiloop(self,jobID, abortEvent): for i in range(0,20,1): time.sleep(0.1) # ここで時間の掛かる処理 wx.CallAfter(self.update,i) # ret = i if abortEvent(): print("Abort") break return(str(ret)) def update(self, data): print('update id: {0}'.format(th.get_ident())) #UIスレッドで実行 self.m_gauge1.SetValue(data)
別スレッドでの処理が終了した時に呼ばれる処理
Startボタンを有効化してAbortボタンを無効化する
この関数はメインスレッドで呼ばれる
Abortによる終了時には呼ばれない
def guiloop_finish(self ,delayedResult): self.m_buttonStart.Enable(True) self.m_buttonAbort.Enable(False) jobID = delayedResult.getJobID() assert jobID == self.jobID try: result = delayedResult.get() except Exception as exc: print( "Result for job %s raised exception: %s" % (jobID, exc) ) return print( "Got result for job %s: %s" % (jobID, result) )
wx.ProgressDialogで進捗の表示とキャンセルをする例
ProgressDialogでメインウインドウがロックされるのでボタンの有効/無効は不要
def m_buttonStartOnButtonClick(self, event ): self.abortEvent.clear() self.jobID += 1 delayedresult.startWorker(self.guiloop_finish, self.guiloop, wargs=(self.jobID,self.abortEvent), jobID=self.jobID) self.max = 80 self.dlg = wx.ProgressDialog("Progress dialog example", "An informative message", maximum = self.max, parent=self, style = 0 | wx.PD_APP_MODAL | wx.PD_CAN_ABORT #| wx.PD_CAN_SKIP #| wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME | wx.PD_REMAINING_TIME #| wx.PD_AUTO_HIDE )
guiloop_finishの最後でProgressDialogをDestroy
def guiloop_finish(self ,delayedResult): jobID = delayedResult.getJobID() assert jobID == self.jobID try: result = delayedResult.get() except Exception as exc: print( "Result for job %s raised exception: %s" % (jobID, exc) ) return print( "Got result for job %s: %s" % (jobID, result) ) self.dlg.Destroy()
guiloopは同じ
def guiloop(self,jobID, abortEvent): ret = 0 for i in range(0,self.max,1): time.sleep(0.1) # ここで時間の掛かる処理 if abortEvent(): print("Abort") break wx.CallAfter(self.update,i) ret = i return(str(ret))
guiloopから進捗を伝達
キャンセルボタンが押されていたらAbortしてProgressDialogをDestroy
def update(self, data): if data >= self.max / 2: (keepGoing, skip) = self.dlg.Update(data, "Half-time!") else: (keepGoing, skip) = self.dlg.Update(data) if not keepGoing: self.abortEvent.set() self.dlg.Destroy()