Python3結合Sciter編寫桌面程序第二節

第二節 將任務添加到隊列!

上一個栗子只是簡單實現了下網頁與後臺的通訊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腳本做爲子進程,也有不少方法可供選擇。但我不知道應該如何通訊及傳參,找了一些栗子,無奈沒法徹底理解,只能待往後解決。oop

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 )

代碼漸漸多了起來,但效果仍是很讓人滿意的。

缺點是一不留神容易出錯

源碼

相關文章
相關標籤/搜索