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

作者:goldensun 时间:2021-03-09 12:02:57 

如果你经常使用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,线程
0
投稿

猜你喜欢

  • ASP强制刷新和判断文件地址是否存在

    2007-09-16 17:11:00
  • asp获取完整url地址代码

    2010-03-22 14:25:00
  • python+opencv实现目标跟踪过程

    2023-03-29 01:29:15
  • php is_numberic函数造成的SQL注入漏洞

    2023-07-18 00:32:25
  • Python爬虫中urllib库的进阶学习

    2023-12-13 19:31:53
  • 几个SQL SERVER应用问题解答

    2008-01-01 19:12:00
  • asp javascript picasa相册外链批量导出

    2011-03-30 10:52:00
  • Python Base64编码和解码操作

    2022-10-20 06:26:05
  • 如何避免SQL语句中含有单引号而导致操作失败?

    2009-11-07 18:40:00
  • 告别网页搜索!教你用python实现一款属于自己的翻译词典软件

    2023-08-08 17:46:58
  • asp如何创建一个PDF文件?

    2009-11-14 20:53:00
  • Python-pip配置国内镜像源的安装方式

    2023-08-22 21:55:38
  • Python绘制K线图之可视化神器pyecharts的使用

    2023-06-28 12:30:58
  • 使用Python爬虫库BeautifulSoup遍历文档树并对标签进行操作详解

    2022-12-17 17:20:07
  • 解读ASP.NET 5 & MVC6系列教程(9):日志框架

    2023-06-30 06:10:57
  • Cpython解释器中的GIL全局解释器锁

    2021-08-29 22:06:43
  • php flv视频时间获取函数

    2023-09-04 13:41:48
  • MySQL的存储过程写法和Cursor的使用

    2008-12-03 15:55:00
  • 关闭窗口时保存数据的办法

    2009-02-19 13:39:00
  • python做量化投资系列之比特币初始配置

    2021-06-28 06:01:31
  • asp之家 网络编程 m.aspxhome.com