• [iPodder]wxPython中多线程处理的研究 转载


     

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

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

    第一条其实是一个原则。

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

    import sys

    from wxPython.wx import *
    from wxPython.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):
            wxPostEvent(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来处理
  • 相关阅读:
    好文!悟透JavaScript
    关于“四舍六入五成双/四舍六入五留双/四舍六入五单双”等口诀的实例研究
    第四个:怎么应付在背地里说你坏话的人
    为什么日全食时开始下雨,10分钟后雨又停了。初中物理知识。
    吃萝卜去口气(口臭)
    今天12点34分56秒是一个神奇的时刻
    sharepoint2010人性化的地方员工离职AD账号禁用(个人网站自动提醒上级经理功能)
    指定为添加到网站中的项目显示“新”图标的天数。
    解决sharepoint2010的多行文本框的插入图片—【从sharepoint】的disabled问题
    sharepoint2010的IE6不支持的解决方法
  • 原文地址:https://www.cnblogs.com/dengyigod/p/3093984.html
Copyright © 2020-2023  润新知