分析Python编程时利用wxPython来支持多线程的方法


Posted in Python onApril 07, 2015

如果你经常使用python开发GUI程序的话,那么就知道,有时你需要很长时间来执行一个任务。当然,如果你使用命令行程序来做的话,你回非常惊讶。大部分情况下,这会堵塞GUI的事件循环,用户会看到程序卡死。如何才能避免这种情况呢?当然是利用线程或进程了!本文,我们将探索如何使用wxPython和theading模块来实现。

wxpython线程安全方法

wxPython中,有三个“线程安全”的函数。如果你在更新UI界面时,三个函数都不使用,那么你可能会遇到奇怪的问题。有时GUI也忙运行挺正常,有时却会无缘无故的崩溃。因此就需要这三个线程安全的函数:wx.PostEvent, wx.CallAfter和wx.CallLater。据Robin Dunn(wxPython作者)描述,wx.CallAfter使用了wx.PostEvent来给应用程序对象发生事件。应用程序会有个事件处理程序绑定到事件上,并在收到事件后,执行处理程序来做出反应。我认为wx.CallLater是在特定时间后调用了wx.CallAfter函数,已实现规定时间后发送事件。

Robin Dunn还指出Python全局解释锁 (GIL)也会避免多线程同时执行python字节码,这会限制程序使用CPU内核的数量。另外,他还说,“wxPython发布GIL是为了在调用wx API时,其他线程也可以运行”。换句话说,在多核机器上使用多线程,可能效果会不同。

总之,大概的意思是桑wx函数中,wx.CallLater是最抽象的线程安全函数, wx.CallAfter次之,wx.PostEvent是最低级的。下面的实例,演示了如何使用wx.CallAfter和wx.PostEvent函数来更新wxPython程序。

wxPython, Theading, wx.CallAfter and PubSub

wxPython邮件列表中,有些专家会告诉其他人使用wx.CallAfter,并利用PubSub实现wxPython应用程序与其他线程进行通讯,我也赞成。如下代码是具体实现:

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.PySimpleApp() 
  frame = MyForm().Show() 
  app.MainLoop()

我们会用time模块来模拟耗时过程,请随意将自己的代码来代替,而在实际项目中,我用来打开Adobe Reader,并将其发送给打印机。这并没什么特别的,但我不用线程的话,应用程序中的打印按钮就会在文档发送过程中卡住,UI界面也会被挂起,直到文档发送完毕。即使一秒,两秒对用户来说都有卡的感觉。

总之,让我们来看看是如何工作的。在我们编写的Thread类中,我们重写了run方法。该线程在被实例化时即被启动,因为我们在__init__方法中有“self.start”代码。run方法中,我们循环6次,每次sheep10秒,然后使用wx.CallAfter和PubSub更新UI界面。循环结束后,我们发送结束消息给应用程序,通知用户。

你会注意到,在我们的代码中,我们是在按钮的事件处理程序中启动的线程。我们还禁用按钮,这样就不能开启多余的线程来。如果我们让一堆线程跑的话,UI界面就会随机的显示“已完成”,而实际却没有完成,这就会产生混乱。对用户来说是一个考验,你可以显示线程PID,来区分线程,你可能要在可以滚动的文本控件中输出信息,这样你就能看到各线程的动向。

最后可能就是PubSub接收器和事件的处理程序了:
 

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()

看我们如何从线程中提取消息,并用来更新界面?我们还使用接受到数据的类型来告诉我们什么显示给了用户。很酷吧?现在,我们玩点相对低级一点点,看wx.PostEvent是如何办的。

wx.PostEvent与线程

下面的代码是基于wxPython wiki编写的,这看起来比wx.CallAfter稍微复杂一下,但我相信我们能理解。

import time 
import wx 
   
from threading import Thread 
   
# 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 
   
######################################################################## 
class TestThread(Thread): 
  """Test Worker Thread Class."""
   
  #---------------------------------------------------------------------- 
  def __init__(self, wxObject): 
    """Init Worker Thread Class."""
    Thread.__init__(self) 
    self.wxObject = wxObject 
    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) 
      amtOfTime = (i + 1) * 10
      wx.PostEvent(self.wxObject, ResultEvent(amtOfTime)) 
    time.sleep(5) 
    wx.PostEvent(self.wxObject, ResultEvent("Thread finished!")) 
   
######################################################################## 
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) 
   
    # Set up event handler for any worker thread results 
    EVT_RESULT(self, self.updateDisplay) 
   
  #---------------------------------------------------------------------- 
  def onButton(self, event): 
    """
    Runs the thread
    """
    TestThread(self) 
    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.PySimpleApp() 
  frame = MyForm().Show() 
  app.MainLoop()

让我们先稍微放一放,对我来说,最困扰的事情是第一块:
 

# 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

EVT_RESULT_ID只是一个标识,它将线程与wx.PyEvent和“EVT_RESULT”函数关联起来,在wxPython代码中,我们将事件处理函数与EVT_RESULT进行捆绑,这就可以在线程中使用wx.PostEvent来将事件发送给自定义的ResultEvent了。

结束语

希望你已经明白在wxPython中基本的多线程技巧。还有其他多种多线程方法这里就不在涉及,如wx.Yield和Queues。幸好有wxPython wiki,它涵盖了这些话题,因此如果你有兴趣可以访问wiki的主页,查看这些方法的使用。

Python 相关文章推荐
python分割和拼接字符串
Nov 01 Python
有关wxpython pyqt内存占用问题分析
Jun 09 Python
浅析Python中的多重继承
Apr 28 Python
MySQL中表的复制以及大型数据表的备份教程
Nov 25 Python
Python中scatter函数参数及用法详解
Nov 08 Python
python3调用R的示例代码
Feb 23 Python
python3中获取文件当前绝对路径的两种方法
Apr 26 Python
wxPython之wx.DC绘制形状
Nov 19 Python
tensorflow的计算图总结
Jan 12 Python
Python3和PyCharm安装与环境配置【图文教程】
Feb 14 Python
将自己的数据集制作成TFRecord格式教程
Feb 17 Python
python 操作excel表格的方法
Dec 05 Python
Python中尝试多线程编程的一个简明例子
Apr 07 #Python
Python的Flask框架中Flask-Admin库的简单入门指引
Apr 07 #Python
用Python实现一个简单的线程池
Apr 07 #Python
浅谈Python程序与C++程序的联合使用
Apr 07 #Python
浅要分析Python程序与C程序的结合使用
Apr 07 #Python
python实现根据用户输入从电影网站获取影片信息的方法
Apr 07 #Python
python中列表元素连接方法join用法实例
Apr 07 #Python
You might like
PHP 七大优势分析
2009/06/23 PHP
mysqli_set_charset和SET NAMES使用抉择及优劣分析
2013/01/13 PHP
PHP中防止SQL注入方法详解
2014/12/25 PHP
PHP编程基本语法快速入门手册
2016/01/07 PHP
php 输入输出流详解及示例代码
2016/08/25 PHP
PHP简单数据库操作类实例【支持增删改查及链式操作】
2016/10/10 PHP
php实现用户注册密码的crypt加密
2017/06/08 PHP
javascript+xml技术实现分页浏览
2008/07/27 Javascript
自定义右键属性覆盖浏览器默认右键行为实现代码
2013/02/02 Javascript
jquery定时滑出可最小化的底部提示层特效代码
2013/10/02 Javascript
Jquery模仿Baidu、Google搜索时自动补充搜索结果提示
2013/12/26 Javascript
将HTML的左右尖括号等转义成实体形式的两种实现方式
2014/05/04 Javascript
javascript实现实时输出当前的时间
2015/04/27 Javascript
JS实现随机乱撞彩色圆球特效的方法
2015/05/05 Javascript
JavaScript实现对下拉列表值进行排序的方法
2015/07/15 Javascript
DOM事件探秘篇
2017/02/15 Javascript
JavaScript实现简单的星星评分效果
2017/05/18 Javascript
详解Vue快速零配置的打包工具——parcel
2018/01/16 Javascript
Nodejs模块的调用操作实例分析
2018/12/25 NodeJs
微信小程序webview与h5通过postMessage实现实时通讯的实现
2019/08/20 Javascript
Python strip lstrip rstrip使用方法
2008/09/06 Python
使用PYTHON接收多播数据的代码
2012/03/01 Python
python的类方法和静态方法
2014/12/13 Python
python 截取 取出一部分的字符串方法
2017/03/01 Python
python skimage 连通性区域检测方法
2018/06/21 Python
pandas 对日期类型数据的处理方法详解
2019/08/08 Python
python实点云分割k-means(sklearn)详解
2020/05/28 Python
吃透移动端 Html5 响应式布局
2019/12/16 HTML / CSS
zooplus波兰:在线宠物店
2019/07/21 全球购物
《宋庆龄故居的樟树》教学反思
2014/04/07 职场文书
绿色小区申报材料
2014/08/22 职场文书
2014年扫黄打非工作总结
2014/12/03 职场文书
青春雷锋观后感
2015/06/10 职场文书
地震捐款简报
2015/07/21 职场文书
煤矿施工安全协议书
2016/03/22 职场文书
小学秋季运动会加油口号及加油稿
2019/08/19 职场文书