Python3结合Sciter编写桌面

发布时间:2019-10-14 09:21:48编辑:auto阅读(2247)

    第二节 将任务添加到队列!

    上一个栗子只是简单实现了下网页与后台的通信

    def clickMe(self):
        #你可以在这里处理任何你想要的操作
        self.call_function('clickCallBack','你已经点到我了!')

    但由于是同一个进程,如果你做了很耗时的操作,比如下载一张图片之类的IO操作......

    你会发现,窗口卡住了,一般表现为窗口泛白,出现未响应的提示......但这并不是程序真的未响应了,等图片下载完就会恢复原样。

    但是,你能接受吗?

    如果能的话......下面就可以不用看了,我说真的。

    咳...嗯

    继续

    为了不卡,我选择了多进程的方式,多线程也可以,但万一这个线程死掉,会拉着主线程下水......以防万一,我选择再开一个进程作为服务进程。

    from multiprocessing import Process,Queue
    
    # 创建用于接收服务进程传递的回馈任务的队列,此队列线程安全
    self.GuiQueue = Queue()
    # 创建用于接收界面进程发送的任务的队列,此队列线程安全
    self.ServiceQueue = Queue()
    p = Process(target = startServiceP, args = ( self.GuiQueue, self.ServiceQueue ))
    p.daemon = True #设置为守护进程,保证主进程退出时子进程也会退出
    p.start()

    为何选用Process及Queue?

    单纯开启一个子进程或许还有一个更好的选择:Popen,它可以启动独立的py脚本作为子进程,也有很多方法可供选择。但我不知道应该如何通信及传参,找了一些栗子,无奈无法完全理解,只能待日后解决。

    Python多进程通信方法有Queue、Pipe、Value、Array

    • pipe用来在两个进程间通信
    • queue用来在多个进程间实现通信
    • Value + Array 是python中共享内存映射文件的方法

    最初的设计比现在复杂,共有3个进程,故弃用pipe

    Value + Array的方式当时没找到,遗漏

    只剩Queue......

    据说Queue速度上慢一些,但以咱目前的水平,速度不是瓶颈

    够用就行,不是吗?

    pipe后期也会研究的就是了......

    我们来看一下这个服务进程有些啥

    def startServiceP(_GuiQueue, _ServiceQueue):
        '''开启一个服务进程'''
        funMap = ServiceEvent( _GuiQueue )
        EventManager( _ServiceQueue, funMap ).Start()

    就这么简单~

    funMap 是啥? ServiceEvent 又哪来的!? EventManager 又是什么鬼??!!

    等下,把刀放下......

    咳...

    一般来说,从界面传来的命令都是字符串,然后通过这个字符串来执行指定函数

    funMap 就是存放的事先写好的函数字典

    看一下ServiceEvent():

    class ServiceEvent(object):
        '''服务进程'''
        def __init__(self, _GuiQueue):
            self.GuiQueue = _GuiQueue
    
        def clickCallBack(self, msg):
            sleep(3)
            self.__putGui( 'clickCallBack', msg )
    
        def __putGui(self, f, m = None ):
            self.GuiQueue.put({
                'fun' : f,
                'msg' : m
            })

    现在可以调用 funMap.clickCallBack()

    关于 GuiQueue 等会再说,先来看一下EventManager()

    clickMe() 只是把要执行的任务发送给 ServiceQueue 了,但此任务不会自动执行,我们还需要一个循环来读取任务,这就是EventManager()的功能。

    EventManager()核心代码:

    def __Run(self):
        while self.__active == True:
            try:
                # 获取事件的阻塞时间设为1秒
                event = self.Queue.get(timeout = 1)
                getattr( self.funMap, event['fun'] )( event['msg'] )  #关键代码
            except Exception as e:
                pass

    以上是服务进程的相关内容,我们再回来看一下界面该如何及时获得反馈

    from threading import Thread
    
    t = Thread(target = queueLoop, args=( self.GuiQueue, self.call_function ))
    t.daemon = True
    t.start()

    嗯,此处我开了另一个线程来执行这个循环,老实说没想到特别好的办法,这个循环肯定不能在主线程使用,会卡界面的,开一个进程又太小题大做,折中方案,用了多线程,好在它只是遍历Queue,没啥复杂的操作......

    def queueLoop( _GuiQueue, funCall ):
        guiCallBack = GuiCallBack( funCall )
        EventManager( _GuiQueue, guiCallBack ).Start()

    基本和服务进程一样,不做过多解释了~

    需要注意的只有 funCall这个参数,很重要,界面的事件调用全靠它。

    Tis:

    $(.click-me).on("click",function(){
        view.clickMe();  //view Sciter内置的对象,所有tis都可调用
    })

    main.py :

    # 导入sciter支持,必须安装pysciter
    import sciter
    import ctypes
    import json
    
    from multiprocessing import Process,Queue
    from threading import Thread
    from EventManager import EventManager
    from FunManager import ServiceEvent, GuiCallBack
    
    # 设置dpi, 防止程序在高分屏下发虚
    ctypes.windll.user32.SetProcessDPIAware(2)
    
    def startServiceP(_GuiQueue, _ServiceQueue):
        '''开启一个服务进程'''
        funMap = ServiceEvent( _GuiQueue )
        EventManager( _ServiceQueue, funMap ).Start()
    
    def queueLoop( _GuiQueue, funCall ):
        guiCallBack = GuiCallBack( funCall )
        EventManager( _GuiQueue, guiCallBack ).Start()
    
    class Frame(sciter.Window):
        def __init__(self):
            '''
                ismain=False, ispopup=False, ischild=False, resizeable=True,
                parent=None, uni_theme=False, debug=True,
                pos=None,  pos=(x, y)
                size=None
            '''
            super().__init__(ismain=True, debug=True)
            self.set_dispatch_options(enable=True, require_attribute=False)
    
        def _document_ready(self, target):
            '''在文档加载后执行,如果设置启动画面,可以在这里结束'''
    
            # 创建用于接收服务进程传递的回馈任务的队列,此队列线程安全
            self.GuiQueue = Queue()
            # 创建用于接收界面进程发送的任务的队列,此队列线程安全
            self.ServiceQueue = Queue()
            p = Process(target = startServiceP, args = ( self.GuiQueue, self.ServiceQueue ))
            p.daemon = True #设置为守护进程,保证主进程退出时子进程也会退出
            p.start()
            t = Thread(target = queueLoop, args=( self.GuiQueue, self.call_function ))
            t.daemon = True
            t.start()
    
        def clickMe(self):
            # 点击页面上的按钮后,只将任务添加到服务队列,耗时很短,因此不会发生界面卡顿现象
            self.__putService('clickCallBack','你已经点到我了!')
    
        def __putService(self, f, m = None):
            '''接收界面事件并转发'''
            self.ServiceQueue.put({
                'fun' : f,
                'msg' : m
            })
    
    if __name__ == '__main__':
        frame = Frame()
        frame.load_file("Gui/main.html")
        frame.run_app()
    

    EventManager.py:

    class EventManager:
        def __init__(self, _Queue, funMap):
            self.__active = False
            self.Queue = _Queue
            self.funMap = funMap
    
        def __Run(self):
            while self.__active == True:
                try:
                    # 获取事件的阻塞时间设为1秒
                    event = self.Queue.get(timeout = 1)
                    getattr( self.funMap, event['fun'] )( event['msg'] )
                except Exception as e:
                    pass
    
        def Start(self):
            self.__active = True
            self.__Run()
    
        def Stop(self):
            self.__active = False

    FunManager.py:

    from time import sleep
    
    class ServiceEvent(object):
        '''服务进程'''
        def __init__(self, _GuiQueue):
            self.GuiQueue = _GuiQueue
    
        def clickCallBack(self, msg):
            sleep(3)
            self.__putGui( 'clickCallBack', msg )
    
        def __putGui(self, f, m = None ):
            self.GuiQueue.put({
                'fun' : f,
                'msg' : m
            })
    
    class GuiCallBack(object):
        def __init__(self, funCall):
            self.funCall = funCall
    
        def clickCallBack(self, msg):
            return self.funCall('clickCallBack', msg )

    代码渐渐多了起来,但效果还是很让人满意的。

    缺点是一不留神容易出错

    源码

关键字