WPF线程模型

WPF线程模型 线程模型 WPF

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库常用于更动态的元素表现,比如实时图表,动画,特效,游戏等,任何一个像素的改变,都会快速的呈现给你的用户。

img

1.1.2. 保持模式GUI

即时模式 API 是过程性的。 每次绘制新帧时,应用程序都会直接发出绘图命令。 图形库不会在帧之间存储场景模型。 相反,应用程序会跟踪场景。

由开发者调用渲染库的API,适时的重绘需需要改变的元素。这就需要渲染库有能力记住之前都渲染了什么东西,所以也会占用更多的内存。

保留模式的GUI库通常更容易使用,使开发更快,但它们也通常也会需要更多的开销,比如要记住元素的位置、层级、遮盖情况等等。

img

保留模式 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 线程的主要职责是:

  1. 计算布局和测量视觉(Visual)对象。
  2. 实现数据绑定和依赖属性系统。
  3. 处理用户输入和事件。
  4. 安排和分派渲染工作项给复合线程。

总的来说,UI 线程的工作是计算最终结果,并安排复合线程执行渲染工作。

2.1.2. 复合线程

复合线程负责 WPF 视觉层次结构的实际渲染工作。当应用程序的 UI 需要在屏幕上重新绘制时,复合线程就会介入,包括窗口大小调整、动画以及任何影响 UI 外观的操作。

复合线程与 UI 线程紧密协作。UI 线程计算布局并安排 Visual 对象,复合线程会渲染 Visual 对象,并将其发送给桌面窗口管理器以在屏幕上显示。

复合线程是一个线程池,包含若干个工作线程,线程数量通常与系统的 CPU 内核数量相同,可以在多个内核上并行工作,从而提高 WPF 应用程序 UI 操作的性能。

复合线程的主要职责是:

  1. 生成可视化树中元素的位图。
  2. 应用效果(如3D变换、混合模式等)。
  3. 合成最终的渲染内容发送给桌面窗口管理器。

2.2. UI线程和复合线程协作

UI 线程和复合线程是 WPF 渲染系统的两个重要部分,它们相互协作完成渲染工作:

  1. UI线程负责计算布局、测量元素大小、响应用户交互等。
  2. UI线程通过 VisualTarget.Render 方法将渲染工作项分派给复合线程线程池。
  3. 复合线程中的工作线程并行执行渲染工作,生成位图数据。
  4. 复合线程将最终的渲染结果提交给桌面窗口管理器显示。

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

评论