分析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 相关文章推荐
完美解决安装完tensorflow后pip无法使用的问题
Jun 11 Python
Python自然语言处理 NLTK 库用法入门教程【经典】
Jun 26 Python
Linux下Pycharm、Anaconda环境配置及使用踩坑
Dec 19 Python
django项目中使用手机号登录的实例代码
Aug 15 Python
python bluetooth蓝牙信息获取蓝牙设备类型的方法
Nov 29 Python
PyQT5 emit 和 connect的用法详解
Dec 13 Python
关于Python中定制类的比较运算实例
Dec 19 Python
使用Python来做一个屏幕录制工具的操作代码
Jan 18 Python
Python requests模块安装及使用教程图解
Jun 30 Python
Python 如何调试程序崩溃错误
Aug 03 Python
Django项目创建及管理实现流程详解
Oct 13 Python
能让Python提速超40倍的神器Cython详解
Jun 24 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下封装较好的数字分页方法
2010/11/23 PHP
PHP查询快递信息的方法
2015/03/07 PHP
PHP CodeIgniter框架的工作原理研究
2015/03/30 PHP
CodeIgniter中使用Smarty3基本配置
2015/06/29 PHP
PHP从零开始打造自己的MVC框架之路由类实现方法分析
2019/06/03 PHP
js 判断checkbox是否选中的操作方法
2012/11/09 Javascript
使用PHP+JQuery+Ajax分页的实现
2013/04/23 Javascript
location.href用法总结(最主要的)
2013/12/27 Javascript
jquery中EasyUI实现同步树
2015/03/01 Javascript
JavaScript设置表单上传时文件个数的方法
2015/08/11 Javascript
轻松实现javascript图片轮播特效
2016/01/13 Javascript
Bootstrap前端开发案例一
2016/06/17 Javascript
老生常谈 js中this的指向
2016/06/30 Javascript
Javascript获取background属性中url的值
2016/10/17 Javascript
JavaScript中数组Array.sort()排序方法详解
2017/03/01 Javascript
微信小程序选择图片和放大预览图片功能
2017/11/02 Javascript
vue 权限认证token的实现方法
2018/07/17 Javascript
详解Vue源码中一些util函数
2019/04/24 Javascript
js+html实现周岁年龄计算器
2019/06/25 Javascript
JS代码优化的8点建议
2020/02/04 Javascript
vue中v-for循环选中点击的元素并对该元素添加样式操作
2020/07/17 Javascript
google广告之另类js调用实现代码
2020/08/22 Javascript
解决windows下Sublime Text 2 运行 PyQt 不显示的方法分享
2014/06/18 Python
python文件操作之目录遍历实例分析
2015/05/20 Python
Python代码缩进和测试模块示例详解
2018/05/07 Python
利用Pandas读取文件路径或文件名称包含中文的csv文件方法
2018/07/04 Python
python中sort和sorted排序的实例方法
2019/08/26 Python
Python中断多重循环的几种方式详解
2020/02/10 Python
python批量替换文件名中的共同字符实例
2020/03/05 Python
Python爬虫之Selenium多窗口切换的实现
2020/12/04 Python
Python Pandas list列表数据列拆分成多行的方法实现
2020/12/14 Python
python爬取2021猫眼票房字体加密实例
2021/02/19 Python
领导班子四风对照检查材料思想汇报
2014/09/26 职场文书
2014年社区矫正工作总结
2014/11/18 职场文书
活动新闻稿范文
2015/07/17 职场文书
2016年乡镇七一建党节活动总结
2016/04/05 职场文书