指南:用 browser.webworker 在瀏覽器后臺線程運行 Python)
編程語言語言運行時編譯器前端【免費下載鏈接】brythonBrython (Browser Python) is an implementation of Python 3 running in the browser項目地址https://gitcode.com/gh_mirrors/br/brython點擊查看免費下載Web Worker 是瀏覽器提供的獨立后臺線程機(jī)制能讓計算密集型的任務(wù)不阻塞主線程的界面響應(yīng)。BrythonBrowser Python通過browser.webworker模塊為 Python 腳本提供了與 Web Worker 的集成能力允許開發(fā)者用純 Python 寫出在主線程創(chuàng)建 Worker、在 Worker 中運行 Python 邏輯、通過消息或遠(yuǎn)程方法調(diào)用雙向通信的完整方案。本文將圍繞官方文檔 webworker.md 展開結(jié)合倉庫源碼www/src/libs/_webworker.js、www/src/builtin_modules.js、www/tests/test_web_worker.py 等深入講解讀完本文后你將掌握基本W(wǎng)orkerParent/WorkerChild消息通信、RPCWorkerParent/RPCWorkerChild遠(yuǎn)程方法調(diào)用以及如何擴(kuò)展出自定義 Worker 類型。一、為什么需要 browser.webworkerBrython 與 Web Worker 的結(jié)合點瀏覽器原生 Web Worker 用postMessage/onmessage進(jìn)行通信腳本以獨立的 JS 文件加載。Brython 讓 Python 腳本也能跑在 Worker 里關(guān)鍵在于兩點環(huán)境檢測Brython 在啟動時會判斷當(dāng)前是否運行在 Worker 中。在 www/src/brython.js 的開頭部分約第 16 行可以看到判定邏輯$B.isWebWorker (undefined ! typeof WorkerGlobalScope) (function typeof importScripts) (navigator instanceof WorkerNavigator)模塊差異化在 Worker 環(huán)境中browser模塊的行為與主線程不同。查看 www/src/builtin_modules.js約第 105 行當(dāng)$B.isWebWorker為真時模塊會設(shè)置browser.is_webworker true刪除window和win名字Worker 中只有self并把browser.self.send設(shè)為postMessage的別名同時document被替換成一個訪問即拋ValueError(document is not available in Web Workers)的占位對象——這意味著 Worker 線程里無法操作 DOM。在這一底層能力之上Brython 提供了兩條面向 Python 開發(fā)者的集成路徑browser.worker以頁面內(nèi)script typetext/python classwebworker聲明 Worker 腳本配合worker.create_worker()創(chuàng)建參見 webworker_mdn.html 與 worker.pybrowser.webworker本文的主角提供WorkerParent/WorkerChild消息通信與RPCWorkerParent/RPCWorkerChild遠(yuǎn)程方法調(diào)用兩套 API。官方文檔 webworker.md 明確指出browser.webworker提供的是基礎(chǔ)的 Web Worker 集成用它以相對輕松的方式在 Web Worker 中運行 Python 腳本目前可用的有兩組類基礎(chǔ)Worker類與RPCWorker類。二、browser.webworker 核心對象與消息模型在使用前先建立整條連接兩側(cè)的角色認(rèn)知對象運行位置職責(zé)WorkerParent主線程創(chuàng)建 Worker向子線程發(fā)送消息、接收回復(fù)WorkerChildWorker 線程接收父線程消息處理后回復(fù)RPCWorkerParent主線程像調(diào)用本地方法一樣調(diào)用子線程中的 Python 方法RPCWorkerChildWorker 線程注冊可被父線程遠(yuǎn)程調(diào)用的方法current_workerWorker 線程當(dāng)前 Worker 內(nèi)可用的WorkerChild或RPCWorkerChild實例Message兩側(cè)通用消息載體包含消息名name、數(shù)據(jù)data與消息 IDidS_RUNNING兩側(cè)通用Worker 狀態(tài)常量表示 Worker 已啟動并準(zhǔn)備接收消息通信的底層單位是Message對象。它的構(gòu)造形式為Message(name, data)例如ww.Message(ping, hi)。文檔特別提醒Worker 從創(chuàng)建到真正就緒需要一段時間就緒標(biāo)志是w.status ww.S_RUNNING不過這不影響你立即發(fā)消息——消息會被排隊等 Worker 就緒后再按順序發(fā)送。三、實戰(zhàn)一基礎(chǔ) Worker 類消息驅(qū)動通信3.1 父線程側(cè)創(chuàng)建 WorkerParent 并發(fā)送消息在頁面主線程中用WorkerParent構(gòu)造一個 Worker。構(gòu)造函數(shù)接收三個參數(shù)腳本 URL要運行的 Python 腳本地址可以是絕對路徑也可以是相對于 Brython 源碼目錄的相對路徑腳本參數(shù)傳給腳本的參數(shù)列表在 Worker 內(nèi)部通過sys.argv讀取環(huán)境變量一個字典在 Worker 內(nèi)部通過os.environ讀取。from browser import webworker as ww w ww.WorkerParent(web_workers/test_worker.py, [1, 2, 3], {test: Ahoj})接著用WorkerCommon.post_message發(fā)送消息。該方法第一個參數(shù)是Message實例第二個參數(shù)want_reply布爾值表示是否需要等待回復(fù)。若需要回復(fù)方法返回一個asyncio.Future當(dāng)回復(fù)到達(dá)時 Future 會被解析resolve還可以傳入第三個參數(shù)timeout秒超時后 Future 會以超時異常解析m ww.Message(ping, hi) r w.post_message(m, want_replyTrue) # r 是一個 asyncio.Futureawait/yield 它即可拿到回復(fù)如果不在乎回復(fù)或預(yù)期對方不會回復(fù)可以省略這兩個參數(shù)w.post_message(ww.Message(quit, None))3.2 子線程側(cè)實現(xiàn) WorkerChild 的消息處理器Worker 腳本本身就是一個普通的 Brython Python 腳本。先導(dǎo)入所需對象再定義針對不同消息的處理器。注意Worker 環(huán)境里不能使用document但可以使用browser.console輸出調(diào)試信息from browser.webworker import current_worker, Message from browser import console from sys import argv from os import environ def pong(self, message, **_): print(Web worker received message (, message.id, ), message.name, message.data) current_worker.post_reply(message, Message(pong, message.data)) def quit(self, *args, **kwargs): current_worker.terminate()這段代碼里有三個關(guān)鍵 APIcurrent_worker.post_reply(message, reply)向父線程發(fā)送針對某條收到消息的回復(fù)。文檔特別強(qiáng)調(diào)回復(fù)消息應(yīng)當(dāng)用post_reply而不是post_message以此表明這條消息是對已接收消息的應(yīng)答current_worker.terminate()終止 Worker相當(dāng)于關(guān)閉子線程current_workerWorkerChild實例僅在 Worker 線程內(nèi)可用主線程中不存在文檔原文此處表述為 in the main thread it is always結(jié)合上下文應(yīng)為主線程中始終不可用使用時以實際環(huán)境為準(zhǔn)。然后將處理器與消息名綁定WorkerCommon.bind_message的第一個參數(shù)是消息名第二個參數(shù)是消息到達(dá)時要調(diào)用的方法current_worker.bind_message(ping, pong) current_worker.bind_message(quit, quit)最后必須調(diào)用current_worker.exec()通知父線程我已就緒可以開始接收消息。否則父線程側(cè)的消息會一直處于排隊狀態(tài)print(Starting test worker with args:, argv, and environment, environ) current_worker.exec()注意sys.argv與os.environ在此處正是構(gòu)造WorkerParent時傳入的參數(shù)與環(huán)境變量——它們從父線程穿越到了子線程的運行時環(huán)境。四、實戰(zhàn)二RPC Worker 類遠(yuǎn)程方法調(diào)用基礎(chǔ)WorkerParent/WorkerChild非常簡潔適合直接搭積木。RPCWorkerParent和RPCWorkerChild就是官方給出的積木示例在不需要處理消息細(xì)節(jié)的場景里把 Worker 中的 Python 方法當(dāng)作在主線程中運行一樣直接調(diào)用。4.1 父線程側(cè)等待就緒后遠(yuǎn)程調(diào)用與基礎(chǔ)示例最大的不同是RPC 模式下必須真正等待 Worker 啟動完成——因為只有 Worker 就緒后才知道它注冊了哪些方法。所以整個流程要包進(jìn)一個協(xié)程舊式寫法用coroutineyield新式 Python 直接async defawait先wait_for_status(ww.S_RUNNING)等待就緒再調(diào)用遠(yuǎn)程方法from browser import webworker as ww from asyncio import coroutine coroutine def main(w): # Wait for the worker to start yield w.wait_for_status(ww.S_RUNNING) # Call the remote add method a yield w.add(10, 20) assert a 30 # Call the remote log method yield w.log(Test output) # Destroy the worker w.terminate() # Create a new instance of the worker w ww.RPCWorkerParent(web_workers/test_rpc.py, [1, 2, 3], {USER: nobody}) # Run the main method main(w)要點拆解w.wait_for_status(ww.S_RUNNING)等待 Worker 狀態(tài)變?yōu)檫\行中返回后即可安全調(diào)用遠(yuǎn)程方法w.add(10, 20)遠(yuǎn)程方法調(diào)用。add必須定義在 Worker 中調(diào)用會返回一個asyncio.Future代表該方法調(diào)用的返回值在協(xié)程里yield或await它會暫停當(dāng)前協(xié)程直到結(jié)果可用w.log(Test output)無返回值的遠(yuǎn)程調(diào)用同樣適用w.terminate()銷毀 Worker調(diào)用的是WorkerCommon.terminate。4.2 子線程側(cè)注冊可遠(yuǎn)程調(diào)用的方法RPC Worker 的實現(xiàn)與基礎(chǔ)版非常相似唯一的差別是不再用bind_message綁定消息處理器而是用RPCWorkerChild.register_method注冊可以被父線程遠(yuǎn)程調(diào)用的方法from browser.webworker import current_worker, Message from browser import console from sys import argv from os import environ def add(x, y): Adds two numbers return x y def log(*args): print(*args) # Register the add and log methods. current_worker.register_method(add) current_worker.register_method(log) # Tell the parent we are ready print(Starting test RPC worker with args:, argv, and environment, environ) current_worker.exec()register_method接收的是函數(shù)對象本身而非名字字符串注冊后父線程即可通過同名屬性調(diào)用。同樣地腳本末尾必須調(diào)用current_worker.exec()向父線程廣播就緒狀態(tài)。值得注意雖然示例導(dǎo)入了Message但 RPC 模式下開發(fā)者通常不需要直接構(gòu)造消息——方法的入?yún)?、出參的序列化與 Future 的解析都由框架在register_method/RPC 調(diào)用層完成。五、進(jìn)階創(chuàng)建自定義 Worker 類型文檔指出WorkerParent/WorkerChild之所以設(shè)計得如此簡單正是為了便于擴(kuò)展。每一類新的 Worker 類型都需要成對實現(xiàn)兩個類Parent 類在主線程中代表 Worker負(fù)責(zé)創(chuàng)建、發(fā)送請求、接收結(jié)果Child 類在 Worker 線程中代表 Worker負(fù)責(zé)處理請求、返回結(jié)果。兩者通過 Parent 類上的CHILD_CLASS屬性綁定它必須是一個字符串內(nèi)容是在 Worker 中可以用于 import 出 Child 類的模塊路徑。例如RPCWorkerParent就把該屬性設(shè)置為browser.webworker.RPCWorkerChild。創(chuàng)建后模塊會自動在 Web Worker 中實例化 Child 類并把實例存入webworker.current_worker。因此你自定義的 Child 邏輯可以直接通過from browser.webworker import current_worker拿到實例進(jìn)行注冊與初始化。整個父類名 CHILD_CLASS 自動實例化的機(jī)制就是browser.webworker可擴(kuò)展架構(gòu)的核心。六、源碼級支撐Brython 的 Worker 加載鏈路browser.webworker面向 Python 側(cè)封裝消息真正把 Python 腳本送進(jìn) Worker 并初始化 Brython 運行時的是底層 JavaScript 實現(xiàn)。理解這條鏈路有助于排查Worker 沒起來消息沒到之類的問題。6.1 Worker 腳本的注冊與加載在頁面中帶有classwebworker的script typetext/python塊或src指向的 Python 文件會被登記到$B.webworkers映射腳本名 → 腳本對象。見 www/src/brython_builtins.js約第 165 行// For all the scripts defined in the page as webworkers, mapping between // script name and its source code $B.webworkers {}加載側(cè)的邏輯在 www/src/loaders.js 的ajax_load_script中當(dāng)請求的腳本被標(biāo)記為 web workers.is_ww時會把源碼同時寫入$B.webworkers[name]和$B.file_cache[filename]供后續(xù)創(chuàng)建 Worker 時取用VFS虛擬文件系統(tǒng)中的文件與 HTTP 請求兩條路徑都做了同樣處理。6.2 運行時引導(dǎo)Blob URL importScripts真正的 Worker 創(chuàng)建邏輯在 www/src/libs/_webworker.js。其中create_worker()會用$B.py2js把 Python 源碼編譯成 JavaScript拼接一段引導(dǎo)頭header其中按需列出 Brython 運行時腳本brython_builtins、py_ast、py2js、python_parser_peg_version等debug 級別大于 2 時逐模塊加載否則直接加載壓縮版brython并用importScripts()在 Worker 內(nèi)加載將編譯結(jié)果與引導(dǎo)頭包進(jìn)Blob通過URL.createObjectURL生成 Worker URLnew Worker(url)啟動用 Promise 隨機(jī) token 做就緒握手Worker 內(nèi)代碼執(zhí)行成功后會self.postMessage(ok_token)主線程據(jù)此 resolve Promise并觸發(fā)onready回調(diào)執(zhí)行出錯則發(fā)送錯誤 token觸發(fā)onerror。這與 webworker_mdn.html 中展示的用法對應(yīng)主線程用worker.create_worker(worker, onready, onmessage)創(chuàng)建Worker 側(cè)用bind(self, message)監(jiān)聽消息、用self.send(...)回發(fā)。倉庫測試 test_web_worker.py 進(jìn)一步驗證了create_worker(test_web_worker, main, onmessage)的三種回調(diào)onready/onmessage/onerror以及結(jié)構(gòu)化克隆數(shù)據(jù)列表、嵌套字典的往返正確性。6.3 相關(guān)的并發(fā)擴(kuò)展倉庫中還有一處與 Worker 相關(guān)的實現(xiàn)WebWorkerExecutorwww/src/Lib/concurrent/futures/webworker.py它參考 CPythonconcurrent.futures的進(jìn)程池模型用submit(fn, *args) Future 的方式把任務(wù)投遞到 Worker 執(zhí)行并提供shutdown(waitTrue)管理生命周期。如果你在browser.webworker之上需要線程池式的并發(fā)抽象這是一個可直接閱讀的參考實現(xiàn)。七、小結(jié)如何選擇通信模式需求推薦模式關(guān)鍵 API發(fā)送自由消息、不關(guān)心回復(fù)基礎(chǔ) WorkerWorkerParentpost_message(msg)發(fā)消息并等待回復(fù)可設(shè)超時基礎(chǔ) Workerpost_message(msg, want_replyTrue, timeout...) Future像本地函數(shù)一樣遠(yuǎn)程調(diào)用RPC WorkerRPCWorkerParentregister_methodwait_for_status擴(kuò)展新的協(xié)議/Worker 類型自定義Parent/Child 成對實現(xiàn) CHILD_CLASS屬性無論選擇哪種模式都有三條鐵律Worker 內(nèi)無法訪問document主線程必須等待或允許排隊Worker 就緒子線程最后一定要調(diào)用current_worker.exec()宣告就緒。掌握這些你就可以用純 Python 在瀏覽器中輕松構(gòu)建后臺計算、圖像處理、數(shù)據(jù)聚合等不阻塞界面的任務(wù)了。贊分享編程語言語言運行時編譯器前端【免費下載鏈接】brythonBrython (Browser Python) is an implementation of Python 3 running in the browser項目地址https://gitcode.com/gh_mirrors/br/brython點擊查看免費下載相關(guān)推薦Pyodide 使用指南在瀏覽器、Web Worker 與 Node.js 中運行 PythonPyodide 使用指南在瀏覽器、Web Worker 與 Node.js 中運行 Python Pyodide 是一個基于 WebAssembly 的 Py科學(xué)計算開發(fā)工具Brython實際應(yīng)用案例研究Brython實際應(yīng)用案例研究 本文通過多個實戰(zhàn)案例深入探討了Brython在Web開發(fā)中的實際應(yīng)用涵蓋了交互式數(shù)據(jù)可視化、實時聊天應(yīng)用、任務(wù)管理看板、響應(yīng)式編程語言語言運行時編譯器前端Brython與Web Workers如何在瀏覽器中實現(xiàn)多線程Python編程Brython與Web Workers如何在瀏覽器中實現(xiàn)多線程Python編程 BrythonBrowser Python是一個在瀏覽器中運行Python編程語言語言運行時編譯器前端上一篇BepInEx 完整安裝與插件加載終極指南免費給 Unity 游戲加模組的快速方法下一篇開源工具破解3DS格式轉(zhuǎn)換難題從CCI到CIA的完整技術(shù)指南創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考