1. 渲染系統概述
WPF 採用保留模式渲染系統 (Retained Mode Rendering System),該系統可分為 UI 執行緒和複合執行緒兩個主要部分,兩者協作完成 WPF 應用程式的渲染工作。
1.1 立即模式GUI和保持模式GUI
圖形 API 可分為保留模式API 和即時模式API。 Direct2D 是一種即時模式 API。 WPF 是保留模式 API 的一個示例。
1.1.1. 立即模式GUI
保留模式 API 是宣告性的。 應用程式從圖形基元(如形狀和線條)構造場景。 圖形庫將場景的模型儲存在記憶體中。 為了繪製幀,圖形庫將場景轉換為一組繪圖命令。 在幀之間,圖形庫將場景保留在記憶體中。 若要更改呈現的內容,應用程式會發出命令來更新場景,例如新增或刪除形狀。 然後,該庫負責重繪場景。
每渲染一幀時(很多機器都可以做到1秒鐘60幀),渲染庫需要執行渲染每個元素的指令,所以這種庫無需記住你已經渲染了什麼東西,反正每次都會全部重繪。會持續消耗你的CPU和GPU資源。
即時模式的GUI庫常用於更動態的元素表現,比如實時圖表,動畫,特效,遊戲等,任何一個畫素的改變,都會快速的呈現給你的使用者。
1.1.2. 保持模式GUI
即時模式 API 是過程性的。 每次繪製新幀時,應用程式都會直接發出繪圖命令。 圖形庫不會在幀之間儲存場景模型。 相反,應用程式會跟蹤場景。
由開發者呼叫渲染庫的API,適時的重繪需需要改變的元素。這就需要渲染庫有能力記住之前都渲染了什麼東西,所以也會佔用更多的記憶體。
保留模式的GUI庫通常更容易使用,使開發更快,但它們也通常也會需要更多的開銷,比如要記住元素的位置、層級、遮蓋情況等等。
保留模式 API 可能更易於使用,因為 API 會為你執行更多工作,例如初始化、狀態維護和清理。 另一方面,它們通常不太靈活,因為 API 施加了自己的場景模型。 此外,保留模式 API 可能具有更高的記憶體要求,因為它需要提供通用場景模型。 使用即時模式 API,可以實現有針對性的最佳化。
2. 執行緒模型
2.1. 概述
WPF 應用程式有兩類執行緒負責渲染:一個用於管理 UI 叫 UI 執行緒,另一個用於處理渲染叫複合執行緒,也叫呈現執行緒。 當 UI 執行緒接收輸入、處理事件、繪製螢幕和執行應用程式程式碼時,複合執行緒透過隱藏方式在後臺高效執行。
2.1.1. UI執行緒
UI 執行緒是 WPF 應用程式最重要的執行緒。它負責處理所有使用者互動事件,如按鈕單擊、選單選擇以及鍵盤和滑鼠輸入。它還負責計算 UI 元素的佈局、處理資料繫結、觸發屬性更改等工作。UI 執行緒是單執行緒的,這意味著在同一時間只能有一個操作在 UI 執行緒上執行。
UI 執行緒在稱為 Dispatcher 的物件內對工作項進行排隊。 Dispatcher 基於優先順序選擇工作項,並執行每一個工作項直到完成。 每個 UI 執行緒必須具有至少一個 Dispatcher,且每個 Dispatcher 都可精確地在一個執行緒中執行工作項。
UI 執行緒也可以啟用多個,由於多執行緒程式既複雜又難以除錯,因此當存在單執行緒解決方案時,應避免使用多執行緒程式。
UI 執行緒的主要職責是:
- 計算佈局和測量視覺(Visual)物件。
- 實現資料繫結和依賴屬性系統。
- 處理使用者輸入和事件。
- 安排和分派渲染工作項給複合執行緒。
總的來說,UI 執行緒的工作是計算最終結果,並安排複合執行緒執行渲染工作。
2.1.2. 複合執行緒
複合執行緒負責 WPF 視覺層次結構的實際渲染工作。當應用程式的 UI 需要在螢幕上重新繪製時,複合執行緒就會介入,包括視窗大小調整、動畫以及任何影響 UI 外觀的操作。
複合執行緒與 UI 執行緒緊密協作。UI 執行緒計算佈局並安排 Visual 物件,複合執行緒會渲染 Visual 物件,並將其傳送給桌面視窗管理器以在螢幕上顯示。
複合執行緒是一個執行緒池,包含若干個工作執行緒,執行緒數量通常與系統的 CPU 核心數量相同,可以在多個核心上並行工作,從而提高 WPF 應用程式 UI 操作的效能。
複合執行緒的主要職責是:
- 生成視覺化樹中元素的點陣圖。
- 應用效果(如3D變換、混合模式等)。
- 合成最終的渲染內容傳送給桌面視窗管理器。
2.2. UI執行緒和複合執行緒協作
UI 執行緒和複合執行緒是 WPF 渲染系統的兩個重要部分,它們相互協作完成渲染工作:
- UI執行緒負責計算佈局、測量元素大小、響應使用者互動等。
- UI執行緒透過 VisualTarget.Render 方法將渲染工作項分派給複合執行緒執行緒池。
- 複合執行緒中的工作執行緒並行執行渲染工作,生成點陣圖資料。
- 複合執行緒將最終的渲染結果提交給桌面視窗管理器顯示。
3. 效能最佳化
若要生成響應迅速、使用者友好的應用程式,訣竅在於透過保持工作項小型化來最大化 Dispatcher 吞吐量。 這樣一來,工作項就不會停滯在 Dispatcher 佇列中,因等待處理而過時。 輸入和響應間任何可察覺的延遲都會讓介面卡頓無響應,帶來糟糕體驗。
需要注意的是,UI執行緒不應該執行長時間的操作,否則會導致UI無響應。相反,應該在後臺執行緒上執行這些任務。
WPF 應用程式在處理大型操作時,如涉及大型計算,或需要查詢某些遠端伺服器上的資料庫。通常情況下,解決方法是在單獨的執行緒中處理大型操作,讓 UI 執行緒更多傾向於處理 Dispatcher 佇列中的工作項。大型操作完成後,再透過 Dispatcher 將結果傳送到 UI 執行緒進行安全渲染。
總的來說,UI 執行緒專注於使用者互動和佈局,而複合執行緒專注於高效呈現 UI。透過正確地利用這兩個執行緒,可以構建響應靈敏且高效的 WPF 應用程式。
參考引用:
https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/advanced/threading-model?view=netframeworkdesktop-4.8&viewFallbackFrom=netdesktop-8.0
https://learn.microsoft.com/zh-cn/windows/win32/learnwin32/retained-mode-versus-immediate-mode
https://zhuanlan.zhihu.com/p/534695668