多线程例子一:
import wx
import threading
import random
class WorkerThread(threading.Thread):
"""
This just simulates some long-running task that periodically sends
a message to the GUI thread.
"""
def __init__(self, threadNum, window):
threading.Thread.__init__(self)
self.threadNum = threadNum
self.window = window
self.timeToQuit = threading.Event()
self.timeToQuit.clear()
self.messageCount = random.randint(10,20)
self.messageDelay = 0.1 + 2.0 * random.random()
def stop(self):
self.timeToQuit.set()
def run(self):
msg = "Thread %d iterating %d times with a delay of %1.4f\n" \
% (self.threadNum, self.messageCount, self.messageDelay)
wx.CallAfter(self.window.LogMessage, msg)
for i in range(1, self.messageCount+1):
self.timeToQuit.wait(self.messageDelay)
if self.timeToQuit.isSet():
break
msg = "Message %d from thread %d\n" % (i, self.threadNum)
wx.CallAfter(self.window.LogMessage, msg)
else:
wx.CallAfter(self.window.ThreadFinished, self)
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Multi-threaded GUI")
self.threads = []
self.count = 0
panel = wx.Panel(self)
startBtn = wx.Button(panel, -1, "Start a thread")
stopBtn = wx.Button(panel, -1, "Stop all threads")
self.tc = wx.StaticText(panel, -1, "Worker Threads: 00")
self.log = wx.TextCtrl(panel, -1, "",
style=wx.TE_RICH|wx.TE_MULTILINE)
inner = wx.BoxSizer(wx.HORIZONTAL)
inner.Add(startBtn, 0, wx.RIGHT, 15)
inner.Add(stopBtn, 0, wx.RIGHT, 15)
inner.Add(self.tc, 0, wx.ALIGN_CENTER_VERTICAL)
main = wx.BoxSizer(wx.VERTICAL)
main.Add(inner, 0, wx.ALL, 5)
main.Add(self.log, 1, wx.EXPAND|wx.ALL, 5)
panel.SetSizer(main)
self.Bind(wx.EVT_BUTTON, self.OnStartButton, startBtn)
self.Bind(wx.EVT_BUTTON, self.OnStopButton, stopBtn)
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
self.UpdateCount()
def OnStartButton(self, evt):
self.count += 1
thread = WorkerThread(self.count, self)
self.threads.append(thread)
self.UpdateCount()
thread.start()
def OnStopButton(self, evt):
self.StopThreads()
self.UpdateCount()
def OnCloseWindow(self, evt):
self.StopThreads()
self.Destroy()
def StopThreads(self):
while self.threads:
thread = self.threads[0]
thread.stop()
self.threads.remove(thread)
def UpdateCount(self):
self.tc.SetLabel("Worker Threads: %d" % len(self.threads))
def LogMessage(self, msg):
self.log.AppendText(msg)
def ThreadFinished(self, thread):
self.threads.remove(thread)
self.UpdateCount()
app = wx.PySimpleApp()
frm = MyFrame()
frm.Show()
app.MainLoop()
官方例子:
import time
import wx
from threading import Thread
from wx.lib.pubsub import Publisher
########################################################################
class TestThread(Thread):
"""Test Worker Thread Class."""
#----------------------------------------------------------------------
def __init__(self):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.start() # start the thread 直接在初始化的时候就开启一个线程了
#----------------------------------------------------------------------
def run(self):
"""Run Worker Thread."""
# This is the code executing in the new thread.
for i in range(6):
time.sleep(10)
wx.CallAfter(self.postTime, i)
time.sleep(5)
wx.CallAfter(Publisher().sendMessage, "update", "Thread finished!")
#----------------------------------------------------------------------
def postTime(self, amt):
"""
Send time to GUI
"""
amtOfTime = (amt + 1) * 10
Publisher().sendMessage("update", amtOfTime) #绑定触发关键字
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
self.displayLbl = wx.StaticText(panel, label="Amount of time since thread started goes here")
self.btn = btn = wx.Button(panel, label="Start Thread")
btn.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.displayLbl, 0, wx.ALL|wx.CENTER, 5)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
# create a pubsub receiver
Publisher().subscribe(self.updateDisplay, "update")
#----------------------------------------------------------------------
def onButton(self, event):
"""
Runs the thread
"""
TestThread()
self.displayLbl.SetLabel("Thread started!")
btn = event.GetEventObject() #用来知道哪个按钮被按了
btn.Disable() #置灰
#----------------------------------------------------------------------
def updateDisplay(self, msg):
"""
Receives data from thread and updates the display
"""
t = msg.data
if isinstance(t, int):
self.displayLbl.SetLabel("Time since thread started: %s seconds" % t)
else:
self.displayLbl.SetLabel("%s" % t)
self.btn.Enable()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App()
MyForm().Show()
app.MainLoop()
我们会用time模块来模拟耗时过程,请随意将自己的代码来代替,而在实际项目中,我用来打开Adobe Reader,并将其发送给打印机。这并没什么特别的,但我不用线程的话,应用程序中的打印按钮就会在文档发送过程中卡住,UI界面也会被挂起,直到文档发送完毕。即使一秒,两秒对用户来说都有卡的感觉。
总之,让我们来看看是如何工作的。在我们编写的Thread类中,我们重写了run方法。该线程在被实例化时即被启动,因为我们在__init__方法中有“self.start”代码。run方法中,我们循环6次,每次sheep10秒,然后使用wx.CallAfter和PubSub更新UI界面。循环结束后,我们发送结束消息给应用程序,通知用户。
你会注意到,在我们的代码中,我们是在按钮的事件处理程序中启动的线程。我们还禁用按钮,这样就不能开启多余的线程来。如果我们让一堆线程跑的话,UI界面就会随机的显示“已完成”,而实际却没有完成,这就会产生混乱。对用户来说是一个考验,你可以显示线程PID,来区分线程,你可能要在可以滚动的文本控件中输出信息,这样你就能看到各线程的动向。
一些问题:
上述代码我们随便搜索下wxpython多线程的应用多会举官方这个例子,可能是版本的问题(本人用的是wx-2.8),第一句就给来个错,网上search一下提出这个Publisher这个moudle不存在的问题是普遍的,但是没有相应的说法,现在就针对这个经典的wxpython程序剖析一下。这段代码本身是简单的,由于版本的问题可能出现如下问题。
1.ImportError: cannot import name Publisher
出现这个错误是正常的,我们进入wx.lib.pubsub这个模块发现并没有Publisher这个类,但是我们在wx.lib.pubsub这个模块下面的pub模块发现了Publisher的影子,原来在2.8版本时已经将这个类私有化了,见126行,_publisher = _Publisher(),同时将Publisher的subscribe与sendMessage复制给了subscribe与sendMessage变量见(128,131)行。所以我们这样引入头:from wx.lib.pubsub import pub,同时将所有的Publisher()改为pub。
2.TypeError: sendMessage() takes exactly 2 arguments (3 given)
经过1的修改,以为大功告成,运行依然报错崩溃。出现这个问题大多数人如果硬找问题原因很难找的,我们debug进出,发现他实例化的是"C:\Python27\Lib\site-packages\wx-2.8-msw-unicode\wx\lib\pubsub\core\kwargs\publisher.py"这个模块下的Publisher类,仔细一看sendMessage方法果然第二个参数应该传个字典类型的所有我们将本程序24,32,71行改为wx.CallAfter(pub.sendMessage, “update”, msg=“Thread finished!”),pub.sendMessage(“update”,msg=amtOfTime),t = msg。运行一下没问题~~注意sendMessage的键值(这里是msg)与subscribe监听方法种的接收参数(这里是msg)要相同,看源码其实就是msgKwargs这个字典参数来传递值的。
其实,这段代码没有问题的。我们注意到”C:\Python27\Lib\site-packages\wx-2.8-msw-unicode\wx\lib\pubsub\core_init_.py" 的43行,其实core这个模块对于Publisher这个类的加载是动态的。我们进入policies.py这个模块第10行,发现msgDataProtocol = ‘kwargs’,原来如此…我们知道core模块下面有2个包一个是arg1,一个是kwargs,我们观察这2个包publisher模块下Publisher类的sendMessage方法是不一样的。原来我们这端代码用的是arg1下的这个Publisher类,好吧,我们将policies.py的第10行改为msgDataProtocol =‘arg1’,还原代码运行正确!!kwargs与arg1不同点是kwargs可以传递多个参数给主程序(**kwargs),而arg1只能是传递一个参数。
经过以上简单的论述,我们知道了wxpython是通过wx.CallAfter给主程序推入事件,通过PubSub与主程序传递数据。关于wxpython多线程的简单理解就是这样了,希望能提供帮助。