上一个栗子只是简单实现了下网页与后台的通讯html
def clickMe(self): #你能够在这里处理任何你想要的操做 self.call_function('clickCallBack','你已经点到我了!')
但因为是同一个进程,若是你作了很耗时的操做,好比下载一张图片之类的IO操做......python
你会发现,窗口卡住了,通常表现为窗口泛白,出现未响应的提示......但这并非程序真的未响应了,等图片下载完就会恢复原样。git
可是,你能接受吗?github
若是能的话......下面就能够不用看了,我说真的。json
咳...嗯安全
继续多线程
为了避免卡,我选择了多进程的方式,多线程也能够,但万一这个线程死掉,会拉着主线程下水......以防万一,我选择再开一个进程做为服务进程。app
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脚本做为子进程,也有不少方法可供选择。但我不知道应该如何通讯及传参,找了一些栗子,无奈没法彻底理解,只能待往后解决。oopPython多进程通讯方法有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 )
代码渐渐多了起来,但效果仍是很让人满意的。
缺点是一不留神容易出错