教學 8 - 使其順~暢

除非您擁有 真正 快速的網路連接,否則您可能會注意到,當您按下按鈕時,應用程式的 GUI 會鎖定一點。這是因為我們發出的 Web 請求是 同步 的。當我們的應用程式發出 Web 請求時,它會等待 API 回傳回應,然後再繼續。在等待時,它 不允許 應用程式重繪 - 結果,應用程式停止回應。

GUI 事件循環(Event loop)

為了理解為什麼會發生這種情況,我們需要深入研究 GUI 應用程式如何運作的細節。具體情況因平台而異;但無論您使用什麼平台或 GUI 環境,概念都是相同的。

從根本上來說,GUI 應用程式是一個看起來像這樣的循環:

while not app.quit_requested():
    app.process_events()
    app.redraw()

此循環稱為 事件循環 。 (這些不是實際的方法名稱 - 它是 偽代碼 中發生的情況的說明)。

當您按一下按鈕、拖曳捲軸或按下按鍵時,代表你產生一個 事件 。該 事件 被放入佇列中,應用程式將在下次有機會處理事件佇列時處理該事件。響應事件而觸發的程式碼稱為 event handler 。這些事件處理程序作為 process_events() 呼叫的一部分被呼叫。

一旦應用程式處理完所有可用事件,它將 redraw() GUI。這考慮了事件對應用程式顯示造成的任何變化,以及作業系統中發生的任何其他變化 - 例如,另一個應用程式的視窗可能會遮蓋或顯示我們應用程式視窗的一部分,我們的應用程式的重繪需要反映目前可見的視窗部分。

需要注意的重要細節:當應用程式正在處理事件時, 它無法重繪 ,並且 它無法處理其他事件

這意味著事件處理程序中包含的任何使用者邏輯都需要快速完成。使用者將觀察到完成事件處理程序的任何延遲,因為 GUI 更新速度會減慢(或停止)。如果延遲足夠長,您的作業系統可能會將此報告為問題 - macOS beachball 和 Windows spinner 圖示是作業系統告訴您您的應用程式在事件處理程序中花費的時間太長。

更新標籤重新計算輸入總數 等簡單操作很容易快速完成。然而,有許多操作無法快速完成。如果您正在執行複雜的數學計算,或對檔案系統上的所有檔案進行索引,或執行網路請求,則您無法 快速完成 - 那些操作本質上很慢。

那麼,我們如何在 GUI 應用程式中執行耗時的操作呢?

非同步程式設計

我們需要的是一種方法讓耗時的event handler執行時告訴應用程序,只要可以從中斷的地方恢復,就可以暫時將控制權釋放回事件循環。由應用程式決定何時釋放它;但如果應用程式定期釋放對事件循環的控制,我們就可以擁有一個長時間運行的事件處理程序 維護一個響應式 UI。

我們可以透過使用 非同步程式設計 來做到這一點。非同步程式設計是一種描述程式的方式,允許解釋器同時運行多個函數,在所有並發運行的函數之間共用資源。

非同步函數(稱為 協程 )需要明確宣告為非同步。他們還需要在內部聲明何時存在將上下文更改為另一個協程的機會。

在Python中,非同步程式設計是使用 asyncawait 關鍵字以及 asyncio 中的模組來實現的。標準庫。 async 關鍵字允許我們宣告函數是非同步協同例程。 await 關鍵字提供了一種聲明何時存在將上下文更改為另一個協同例程的機會的方法。 asyncio 模組為非同步程式設計提供了一些其他有用的工具和語法。

使教學異步

要使我們的教程異步,請修改 say_hello() 事件處理程序,使其如下所示:

async def say_hello(self, widget):
    async with httpx.AsyncClient() as client:
        response = await client.get("https://jsonplaceholder.typicode.com/posts/42")

    payload = response.json()

    self.main_window.info_dialog(
        greeting(self.name_input.value),
        payload["body"],
    )

與先前的版本相比,此程式碼僅發生了 4 處變更:

  1. 該方法被定義為 async def ,而不僅僅是 def 。這告訴 Python 該方法是一個非同步協同例程。

  2. 建立的客戶端是異步 AsyncClient() ,而不是同步 Client() 。這告訴 httpx 它應該以非同步模式運行,而不是同步模式。

  3. 用於建立客戶端的上下文管理器被標記為 async 。這告訴Python,當進入和退出上下文管理器時,有機會釋放控制權。

  4. get 呼叫是使用 await 關鍵字進行的。這指示應用程式在等待網路回應時,應用程式可以釋放對事件循環的控制。

Toga 允許您使用常規方法或非同步協同例程作為處理程序; Toga 管理幕後的一切,以確保根據需要呼叫或等待處理程序。

如果您儲存這些變更並重新執行應用程式(在開發模式下使用 briefcase dev ,或透過更新並重新執行打包的應用程式),應用程式不會有任何明顯的變更。但是,當您單擊按鈕觸發對話框時,您可能會注意到一些細微的改進:

  • 該按鈕返回到 未單擊 狀態,而不是停留在 單擊 狀態。

  • 「沙灘球」/」漏斗」 圖示不會再出現

  • 如果您在等待對話方塊出現時移動/調整應用程式視窗的大小,則該視窗將會重新繪製。

  • 如果您嘗試開啟應用程式選單,該選單將立即出現。

下一步

我們現在擁有一個流暢且響應迅速的應用程序,即使它正在等待緩慢的 API。但是,當我們繼續進一步開發應用程式時,如何確保該應用程式繼續運行?我們如何測試我們的應用程式?前往 教學 9 找找看…