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

如果你经常使用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的主页,查看这些方法的使用。

时间: 2015-04-04

wxpython学习笔记(推荐查看)

一.简介 wxPython是Python编程语言的一个GUI工具箱.他使得Python程序员能够轻松的创建具有健壮.功能强大的图形用户界面的程序.它是Python语言对流行的wxWidgets跨平台GUI工具库的绑定.而wxWidgets是用C++语言写成的.和Python语言与wxWidgets GUI工具库一样,wxPython是开源软件.这意味着任何人都可以免费地使用它并且可以查看和修改它的源代码,或者贡献补丁,增加功能.wxPython是跨平台的.这意味着同一个程序可以不经修改地在多种平

wxPython中文教程入门实例

wxPython中文教程入门实例 wx.Window 是一个基类,许多构件从它继承.包括 wx.Frame 构件.可以在所有的子类中使用 wx.Window 的方法. wxPython的几种方法:* SetTitle( string title ) -- 设置窗口标题.只可用于框架和对话框. * SetToolTip( wx.ToolTip tip ) -- 为窗口添加提示. * SetSize( wx.Size size ) -- 设置窗口的尺寸. * SetPosition( wx.Poin

wxPython 入门教程

这篇文章是关于 wxPython,但 wxPython 实际是两件事物的组合体:Python 脚本语言和 GUI 功能的 wxWindows 库(关于 wxWindows 的介绍,请参阅 developerWorks上的 "细述 wxWindows" ).wxWindows 库是为了最大可移植性的 C/C++ 库,而抽取 GUI 功能.所以 wxWindows 应用程序与生俱来地可以运行在 Windows.带 X.KDE 或 Gnome 的 UNIX 或者 wxWindows 已移植到

python之wxPython菜单使用详解

本文实例讲述了python中wxPython菜单的使用方法,分享给大家供大家参考.具体如下: 先来看看下面这段代码: import wx APP_EXIT=1 #定义一个控件ID class Example(wx.Frame): def __init__(self, parent, id, title): super(Example,self).__init__(parent, id, title) #调用你类的初始化 self.InitUI() #调用自身的函数 def InitUI(self

Python实例之wxpython中Frame使用方法

本节为大家分享的例子是wxpython Frame的用法. 例子: 复制代码 代码如下: #!/usr/bin/python  # -*- coding: GBK -*-  # simple.py import wx app = wx.App()  frame = wx.Frame(None)  frame.Show()  app.MainLoop() 例2, 复制代码 代码如下: #!/usr/bin/python告诉程序 python 解释器的路径,只是在 linux 系统下有用,在 Win

wxPython使用系统剪切板的方法

本文实例讲述了wxPython使用系统剪切板的方法.分享给大家供大家参考.具体如下: 程序运行效果如下图所示: 主要代码如下: import wx ######################################################################## class ClipboardPanel(wx.Panel): """""" #--------------------------------------

windows下wxPython开发环境安装与配置方法

安装文件准备: 安装文件 下载地址 python-2.6.2.msi http://www.python.org/download/ wxPython2.8-win32-unicode-2.8.10.1-py26.exe wxPython2.8-win32-docs-demos-2.8.10.1.exe http://www.wxpython.org/download.php py2exe-0.6.9.win32-py2.6.exe http://sourceforge.net/projects

在Ubuntu系统下安装使用Python的GUI工具wxPython

(一)wxpython的安装 Ubuntu下的安装,还是比较简单的. #使用:apt-cache search wxpython 测试一下,可以看到相关信息 dizzy@dizzy-pc:~/Python$ apt-cache search wxpython cain - simulations of chemical reactions cain-examples - simulations of chemical reactions cain-solvers - simulations of

使用wxpython实现的一个简单图片浏览器实例

上次我爬了n多图片,但是浏览的时候有一个问题. 图片浏览器的浏览一般都是按名称排的,而我对图片的命名是按照数字递增的.比如3总是会排在10后面,也就无法快速地浏览图片了. 所以,出于方便自己查阅图片,也出于学习,决定做一个自己的图片浏览器. 目标:浏览目录,通过滚轮不断显示同一个文件夹下的图片,并自定义排序. 步骤0:要实现图形界面,我使用wxPython. 至于如何安装和简单地使用wxpython,可以到网上检索,一大堆资料. 以下步骤默认你已经知道如何生成一个自己的frame. 步骤1:浏览

wxpython中利用线程防止假死的实现方法

前段时间我编写了一个工业控制的软件,在使用中一直存在一个问题,就是当软件检索设备时,因为这个功能执行的时间比较长,导致GUI界面假死,让用户分辨不清楚软件到底仍在执行,还是真的挂掉了.(虽然我设计了同步log显示,但是这个也同样假死了) 程序截图如下: 代码解析如下: # -*- coding: utf-8 -*- import time import wx from threading import Thread from wx.lib.pubsub import Publisher time

PHP使用curl_multi_select解决curl_multi网页假死问题的方法

本文实例讲述了PHP使用curl_multi_select解决curl_multi网页假死问题的方法.分享给大家供大家参考,具体如下: curl_multi可以批处理事务,给网页编程带来很大的方便.不过在使用curl_multi的过程中,我们会遇到一个比较头疼的问题,那就是当并发处理的事务数量过多的时候,就会出现CPU过高,网页假死的现象,这是不可以忽视的. 今天,通过查询相关资料和测试,终于找到了一个解决问题的方法. 正常情况下,我们是这样使用curl_multi的. 实例代码: $conno

在Vue组件化中利用axios处理ajax请求的使用方法

本文主要给大家介绍了关于在Vue组件化中利用axios处理ajax请求的使用方法,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 推荐方式 首先在 main.js 中引入 axios // 引入 axios import axios from 'axios' 这时候如果你想在其它的组件中使用axios进行ajax请求是或提示报错的,报错内容大致是axios is undefined. 我们通常的决绝方案是将axios改写为 Vue 的原型属性,如2 将axios写入Vue的原型

java中利用反射调用另一类的private方法的简单实例

我们知道,Java应用程序不能访问持久化类的private方法,但Hibernate没有这个限制,它能够访问各种级别的方法,如private, default, protected, public. Hibernate是如何实现该功能的呢?答案是利用JAVA的反射机制,如下: import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ReflectDemo {

JavaScript中利用Array和Object实现Map的方法

本文实例讲述了JavaScript中利用Array和Object实现Map的方法.分享给大家供大家参考.具体如下: 昨天突然看到以前别人用JavaScript实现的Map感觉很不错,但是发现有个别方法有问题,顺便完善了下,添加了 remove .indexOf .values.clear等方法. /** * @author blune68 * @version 0.1, 07/27/12 * */ function Map(){ this.keys = new Array(); this.dat

javascript中利用柯里化函数实现bind方法【推荐】

• 柯理化函数思想:一个js预先处理的思想:利用函数执行可以形成一个不销毁的作用域的原理,把需要预先处理的内容都储存在这个不销毁的作用域中,并且返回一个小函数,以后我们执行的都是小函数,在小函数中把之前预先存储的值进行相关的操作处理即可: • 柯里化函数主要起到预处理的作用: • bind方法的作用:把传递进来的callback回调方法中的this预先处理为上下文context; /** * bind方法实现原理1 * @param callback [Function] 回调函数 * @par

Android中利用C++处理Bitmap对象的实现方法

相信有些Android&图像算法开发者和我一样,遇到过这样的状况:要对Bitmap对象做一些密集计算(例如逐像素的滤波),但是在java层写循环代码来逐像素操作明显是不现实的,因为Java代码的运行速度太慢,而一副很小的240*320图像就有76800个像素,如果考虑到RGB三通道(或者ARGB四通道),还要对这个数量乘以3/4.因此对图像的密集计算一般都利用Jni接口,用C++实现.那么问题来了,怎么把Bitmap中的像素数据从Java层传到C++层? 做法1:之前的做法 我之前的做法是这样的

在python中利用KNN实现对iris进行分类的方法

如下所示: from sklearn.datasets import load_iris iris = load_iris() print iris.data.shape from sklearn.cross_validation import train_test_split X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size = 0.25, random_state = 3

IIS假死的解决方法 缩短IIS应用池回收时间来实现减少IIS假死

IIS日志: 应用程序:ISAPI 'C:\WINDOWS\system32\inetsrv\asp.dll' 报告它自身有问题,原因如下: 'ASP 不正常,因为执行请求的 100% 被挂起,而且请求队列已经使用了 0%.'. 关于 server 2003+IIS6 出现 'ASP 不正常,因为执行请求的 100% 被挂起 现像如下: 站点无法打开,或者打开很慢.HTML可以打开.重新启动或者回收应用程序池可恢复.但过一段时间又会出现 日志里会有: ISAPI 'C:\WINDOWS\syst

C# WinForm程序处理后台繁忙导致前台控件假死现象解决方法

特别是针对循环或timer处理中需要在窗体控件显示数据时,因后台处理过度繁忙而出现没刷新或者假死现象时,可以使用 复制代码 代码如下: Application.DoEvents(); Application.DoEvents()的作用 复制代码 代码如下: private void button1_Click(object sender, EventArgs e)         {             for (int i = 0; i < 10000; i++)