wxPython中多线程处理的研究

在GUI中进行多线程编程是一件很麻烦的事情,一直以来我都在寻找一个通用的方便的处理方法。在前一段时间中我曾经发表过关于长流程的处理,主要是在处理中插入一个对调度器的处理,而这个调度器使用了队列来实现子线程与主线程之间的数据通信。它的确可以解决一些问题,但并不是非常的方便。那么总结在 wxPython 中所提出的解决多线程问题的答案如下:

  1. 不要在子线程中进行GUI的更新处理,所有的GUI的更新全部由GUI线程(主线程)来完成
  2. 使用自定义事件来定义一个事件,然后就可以使用wxPostEvent来发送这个事件,这样会将这个事件放入主线程的事件循环中,从而使用事件得以安全的处理。
  3. 使用线程安全的队列(Queue)来处理主线程与子线程的进程序通讯。
  4. 再有wxPython提供了方便的wxCallAfter()方法来实现wxPostEvent的处理。

第一条其实是一个原则。

第二条应该是很常见的方法,它与第四条非常接近。但第四条是一种最简单的情况,有些复杂的情况使用第二条为好。比如:在更新GUI后会有一些返回值,那么这个返回值我需要进一步进行处理。如果使用第四条,则返回值是无法处理的,因此使用第二条则更为方便。而iPodder则主要使用了第二条。它使用了一个通过的类似wxCallAfter的方式,它使用了一个Mixin模块,这个模块创建了一个新的事件并提供了一个Mixin类。这个类可以响应事件,并且有一个线程安装的调用来处理GUI的更新(其实就是使用了wxPostEvent方法)。这个模块的内容为:

import sys

import wx
from wx.lib import newevent

DispatchEvent, EVT_DISPATCH = newevent.NewEvent()

class GenericDispatchMixin:
    def __init__(self):
        EVT_DISPATCH(self, self.OnDispatchEvent)

    def OnDispatchEvent(self, event):
        event.method(*event.arguments)

    def ThreadSafeDispatch(self, method, *arguments):
        wx.PostEvent(self, DispatchEvent(method = method, arguments = arguments))

这个类很简单,需要注意的就是这个类本身并不是独立使用的,它需要与一个窗体元素相结合,因为对于事件的绑定处理(此处为EVT_DISPATCH)只有窗体元素才可以做到。因此它可以对自定义事件进行绑定,然后可以对其进行响应处理。响应处理很简单,就是对传入的方法参数进行调用。同时它还提供了对某个方法的线程安全的调用,也就是将需要更新GUI的代码首先封装成方法,然后使用ThreadSafeDispatch()对这个方法进行事件处理。在这个处理过程中我的体会为:

  1. 事件响应的处理与事件的调用其实是可以分离的。因为事件响应的处理一般在主线程,而事件的调用一般在子线程。如果放在一起,那么需要将这个UI对象作为参数传入子线程中即可。因此在iPodder中有一些代码看上去就象:

    self.caller.ThreadSafeDispatch()

  2. 需要将GUI更新的处理封装成方法,然后由主线程进行调用。因此上在调用完GUI的更新处理后其实是进行了事件循环,这样GUI的更新结果并不能直接返回。如果必需要知道GUI的更新结果,则可以通过第三种方式,采用Queue来进行主线程与子线程间的数据交易,从而达到同步。否则可以不理会GUI的更新结果继续处理,而在这种情况下,GUI的更新只是被动地表现子线程的处理。

第三条适合进行主线程与子线程间的同步及数据交换。如果GUI只是被动地改变,使用Queue并不方便。因为采用事件方式你需要写一个事件的响应处理,这样何时被调用是由wxPython来完成的。而使用Queue则做不到,可以还要在某个事件中增加对Queue中数据进行处理的代码,如IDLE事件,但这样并不方便。因此建议只是用来做处理同步及数据交换。而以前我介绍过的长时间处理就是采用Queue方式,现在想一想并不是多么方便的处理。

第四条则是第二条的简化,如果你只是更新GUI,而且不关心更新后的返馈结果,那么使用这条最为方便。

为了测试我修改了以前的longtime.py程序,改用GenericDispatch来处理,代码如下:

#coding=cp936

import wx
import time
import threading
import Queue
import traceback
import sys
from GenericDispatch import GenericDispatchMixin

class MainApp(wx.App):

    def OnInit(self):
        self.frame = MainFrame()
        self.frame.Show(True)
        self.SetTopWindow(self.frame)
        return True

class MainFrame(wx.Frame, GenericDispatchMixin):

    def __init__(self):
        wx.Frame.__init__(self, None, -1, u’长运行测试’)
        GenericDispatchMixin.__init__(self)

        box = wx.BoxSizer(wx.HORIZONTAL)

        self.ID_BTN = wx.NewId()
        self.btn = wx.Button(self, self.ID_BTN, u’开始’, size=(60, 22))
        box.Add(self.btn, 0, wx.ALIGN_CENTRE|wx.ALL, 0)
        
        wx.EVT_BUTTON(self.btn, self.ID_BTN, self.OnStart)
        
        self.SetSizerAndFit(box)
        
        
    def OnStart(self, event):
        self.progress = wx.ProgressDialog(u"运行…", u"正在处理请稍候…", 100, style=wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT)
        self.t = TRun(self)
        self.t.setDaemon(True)
        self.t.start()
        
class TRun(threading.Thread):
    def __init__(self, caller):
        threading.Thread.__init__(self)
        self.caller = caller
        self.flag = True

    def run(self):
        for i in range(100):
            print ‘run %d’ % (i+1)
            self.caller.ThreadSafeDispatch(self.update, i)
            if not self.flag:
                self.destroy()
                print ‘flag’, self.flag
                return
            time.sleep(0.1)
        self.destroy()

    def setFlag(self, flag):
        self.flag = flag

    def update(self, i):
        self.flag = self.caller.progress.Update(i+1)
        print self.flag

    def destroy(self):
        self.caller.ThreadSafeDispatch(self.caller.progress.Destroy)
    
app = MainApp(0)

app.MainLoop()

如果你读过以前的Blog,你会发现这段代码不再区别Controller 和View了,而且处理都在子线程中完成,不过它是通过调用self.caller的相关的方法和对象来实现的,因此处理是在子线程中,但真正的元素都是self.caller中的。这样在创建子线程时,将需要处理的UI对象传给子线程,如果处理由子线程来完成,这样写起代码来更方便。要注意的就是在更新时需要调用self.caller的ThreadSafeDispatcah()方法来处理GUI更新的代码。

上面的代码不是最佳的,但是可用的。

这样可以总结一下多线程代码的编写要点:

  1. 将长流程处理写为线程方式
  2. 传入要改变的GUI对象
  3. 在子线程中把对GUI的更新代码写为方法
  4. 调用GUI对象的ThreadSafeDispatchc()方法来安全地调用GUI的更新方法
  5. 需要同步或复杂数据交易时采用Queue来处理
引自:http://blog.donews.com/limodou/archive/2005/08/15/509966.aspx

你可能感兴趣的:(Python)