上一個栗子只是簡單實現了下網頁與後臺的通訊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 )
代碼漸漸多了起來,但效果仍是很讓人滿意的。
缺點是一不留神容易出錯