Intro
總的來說就是處理掉長時間任務(LongRunningTasks),防止由wxPython繪制的GUI出現卡死狀態。
這個問題我找了很久,國內論壇啥的都是千篇一律三個字“多線程”,讓人一臉懵逼。我也不是那種勤奮到去買書買視頻求解的好孩子,只好fαnqiαng去找了。
所以我就不得不出來解決下這個問題了。
原文地址:https://wiki.wxpython.org/LongRunningTasks
翻譯
介紹
以下內容于2001年5月24日發布到comp.lang.python新聞組。由于它很好地解釋了當應用程序執行長時間運行任務時保持GUI響應的選項,我決定將其逐字復制。非常感謝David Bolen花費時間和精力撰寫和發表本文。--Robin
問題
Daniel Frame寫道:
基本上,我已經構建了一個wxPython的GUI,當按下“START”按鈕時,它運行著很長的功能。當然,GUI被鎖定,直到這個功能完成。我想要在我的應用程序中添加一個“停止”按鈕,如果用戶已經厭倦了等待它完成,這個按鈕將停止這個其他功能。 是否可以在不使用線程的情況下完成此任務? 如果是這樣,我怎么可以讓我的運行功能有時候檢查是否按下了停止按鈕? 我聽說過wxyield,但我似乎無法正確實現。
David回復:
有三種可能性我會想到 - 線程, wxYield,或者在wxEVT_IDLE處理程序中進行處理。我會給出下面的一個小樣本。
我喜歡用線程處理這種事(我想他們給UI留足了響應能力),但沒有固定而快的鐵則。所以選隨意一個你覺得更舒服的。
線程真的不是那么難 - 你可以啟動一個線程做你的處理,并讓它在當它完成時發送一個事件到你的主要GUI線程。當它正在工作時,它可以檢查事件對象或一些其他標志變量來表示它應該放棄并停止。
對于一個簡單的例子,這里有一小段代碼 一個簡單的(無法視覺表達的)框架,并使用工作線程模擬一些處理(需要10秒,結果的值為10), 同時允許它中止。這可以推斷為做任何長時間的處理并返回任何結果。 中間事件可以在處理過程中產生一些跡象表明主要的GUI線程是如何進行的 (可能更新一些大小規格或其他視覺上的效果)。
import time
from threading import *
import wx
# Button definitions
ID_START = wx.NewId()
ID_STOP = wx.NewId()
# Define notification event for thread completion
EVT_RESULT_ID = wx.NewId()
def EVT_RESULT(win, func):
"""Define Result Event."""
win.Connect(-1, -1, EVT_RESULT_ID, func)
class ResultEvent(wx.PyEvent):
"""Simple event to carry arbitrary result data."""
def __init__(self, data):
"""Init Result Event."""
wx.PyEvent.__init__(self)
self.SetEventType(EVT_RESULT_ID)
self.data = data
# Thread class that executes processing
class WorkerThread(Thread):
"""Worker Thread Class."""
def __init__(self, notify_window):
"""Init Worker Thread Class."""
Thread.__init__(self)
self._notify_window = notify_window
self._want_abort = 0
# This starts the thread running on creation, but you could
# also make the GUI thread responsible for calling this
self.start()
def run(self):
"""Run Worker Thread."""
# This is the code executing in the new thread. Simulation of
# a long process (well, 10s here) as a simple loop - you will
# need to structure your processing so that you periodically
# peek at the abort variable
for i in range(10):
time.sleep(1)
if self._want_abort:
# Use a result of None to acknowledge the abort (of
# course you can use whatever you'd like or even
# a separate event type)
wx.PostEvent(self._notify_window, ResultEvent(None))
return
# Here's where the result would be returned (this is an
# example fixed result of the number 10, but it could be
# any Python object)
wx.PostEvent(self._notify_window, ResultEvent(10))
def abort(self):
"""abort worker thread."""
# Method for use by main thread to signal an abort
self._want_abort = 1
# GUI Frame class that spins off the worker thread
class MainFrame(wx.Frame):
"""Class MainFrame."""
def __init__(self, parent, id):
"""Create the MainFrame."""
wx.Frame.__init__(self, parent, id, 'Thread Test')
# Dumb sample frame with two buttons
wx.Button(self, ID_START, 'Start', pos=(0,0))
wx.Button(self, ID_STOP, 'Stop', pos=(0,50))
self.status = wx.StaticText(self, -1, '', pos=(0,100))
self.Bind(wx.EVT_BUTTON, self.OnStart, id=ID_START)
self.Bind(wx.EVT_BUTTON, self.OnStop, id=ID_STOP)
# Set up event handler for any worker thread results
EVT_RESULT(self,self.OnResult)
# And indicate we don't have a worker thread yet
self.worker = None
def OnStart(self, event):
"""Start Computation."""
# Trigger the worker thread unless it's already busy
if not self.worker:
self.status.SetLabel('Starting computation')
self.worker = WorkerThread(self)
def OnStop(self, event):
"""Stop Computation."""
# Flag the worker thread to stop if running
if self.worker:
self.status.SetLabel('Trying to abort computation')
self.worker.abort()
def OnResult(self, event):
"""Show Result status."""
if event.data is None:
# Thread aborted (using our convention of None return)
self.status.SetLabel('Computation aborted')
else:
# Process results here
self.status.SetLabel('Computation Result: %s' % event.data)
# In either event, the worker is done
self.worker = None
class MainApp(wx.App):
"""Class Main App."""
def OnInit(self):
"""Init Main App."""
self.frame = MainFrame(None, -1)
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
if __name__ == '__main__':
app = MainApp(0)
app.MainLoop()
哦,如果你擔心掛起在一個退出,如果你的線程由于某種原因沒有終止,只需在init中添加一個self.setDaemon(1)
,Python就不會等待它終止。
【下面是wxYield的……我先偷個懶哈如果要翻譯留言下我就有動力了23333】