多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

WPF UI 内存越跑越多?wpfui 图像缓存与资源释放完整指南

WPF UI 内存越跑越多?wpfui 图像缓存与资源释放完整指南 WPF UI 内存越跑越多wpfui 图像缓存与资源释放完整指南【免费下载链接】wpfuiWPF UI provides the Fluent experience in your known and loved WPF framework. Intuitive design, themes, navigation and new immersive controls. All natively and effortlessly.项目地址: https://gitcode.com/GitHub_Trending/wp/wpfui如果你的 WPF 应用用了 wpfui 控件库运行一两天后任务管理器里的私有内存只涨不跌原因多半不在深层架构而是三件具体的事图片反复解码、解码结果没冻结、窗口关了事件没摘。这篇文章按启动、运行、关闭三个阶段把图像缓存策略和资源释放的落点讲清楚读完你能把内存曲线从持续上涨压回平台期。启动期先量出基线确认增长不是错觉谈优化之前先确认增长真的存在。打开任务管理器展开应用进程记录刚启动时的私有字节数再手动跑一遍你的典型流程——切换 10 次页面、把图片列表滚几遍——记录第二次读数。两次读数一致就没有泄漏不用动任何代码。如果确有增长用 Visual Studio 内存分析器在流程前后各拍一张快照看差值里哪个类型涨得最多。图像多的应用里BitmapSource/BitmapImage通常是大头。数量级可以心算一张 1920×1080 的图解码成 BGRA 后占 1920×1080×4 ≈ 8.3MB连续翻 100 张大图半小时后私有内存从 240MB 涨到 1.1GB示例数据具体数值随机器而定。如果你的曲线是这种形态直接进入下面的缓存部分。运行期一给频繁换图的 Image 垫一层 LRU 缓存这一节解决翻页换图造成的重复解码。用户在页面间来回切换每次ui:Image的 Source 都重新走一遍文件读取和解码旧图像对象要等 GC 才回收但峰值内存已经产生。wpfui 的 Image 控件src/Wpf.Ui/Controls/里 Source 只是一个依赖属性没有内置缓存。解法是在它前面垫一层按图片 URI 缓存解码好的BitmapImage容量满了就踢掉最久没被访问的那张——最久没用的先出局这就是 LRU。缓存创建时设了SizeLimit本例 20MB尺寸按像素量折算缓存自身被限死在上限内public BitmapImage GetBitmap(Uri uri) { if (_imageCache.TryGetValue(uri, out BitmapImage? hit)) { return hit; } var image new BitmapImage(uri) { CacheOption BitmapCacheOption.OnLoad }; _imageCache.Set(uri, image, new MemoryCacheEntryOptions { Size image.PixelWidth * image.PixelHeight / 256_000, SlidingExpiration TimeSpan.FromMinutes(10) }); return image; }SlidingExpiration是第二道保险一张图 10 分钟没人再看就算容量没满也会被回收。容量按用户一次会翻多少张定30~50 张足够不必缓存整个相册。运行期二BitmapImage 解码完就 Freeze别让每处引用各存一份这一节解决同一张图被多处引用时的内存翻倍。缩略图列表、预览大图、窗口图标同时用到同一张图如果对象没冻结WPF 可能为每个引用点各留一份解码数据3 份 8MB 就是 24MB。Freeze 用大白话说给对象上锁之后不许再改WPF 换来保证内存里只有一份解码数据且任何线程都能直接使用。关键动作是解码完成后立刻冻结并且把解码放到 UI 线程之外避免大图卡住主线程private static BitmapImage DecodeFreezed(Uri uri) { var image new BitmapImage(); using var stream File.OpenRead(uri.LocalPath); image.BeginInit(); image.CacheOption BitmapCacheOption.OnLoad; image.StreamSource stream; image.EndInit(); image.Freeze(); return image; }CacheOption.OnLoad与Freeze()要配合使用OnLoad 把文件一次性读进内存并释放流冻结后对象可以安全地送回 UI 线程被多个控件共享。调用方把DecodeFreezed包进Task.Run里跑结果回到 UI 线程再赋给控件。如果配合上一节的缓存使用就把它放进GetBitmap的加载回调中。运行期三长列表换虚拟化控件别让 500 张图同时解码这一节解决长列表一次性创建全部容器的问题。商品列表 500 项、每项一张缩略图普通ItemsControl会给 500 项全部创建容器触发 500 次解码内存还没开始用就先跳一截。虚拟化用大白话说只创建屏幕上看得见的容器滚出视口的容器被回收复用。wpfui 的 ListBox 默认就开着IsVirtualizing自绘布局时库里还有VirtualizingItemsControl控件自带虚拟化面板用法和普通 ItemsControl 一致ui:VirtualizingItemsControl ItemsSource{Binding Products} ui:VirtualizingItemsControl.ItemTemplate DataTemplate ui:Image Source{Binding Thumb} CornerRadius4 / TextBlock Text{Binding Name} / /DataTemplate /ui:VirtualizingItemsControl.ItemTemplate /ui:VirtualizingItemsControl和前两节串起来看Binding Thumb应返回已冻结、已进缓存的BitmapImage。虚拟化回收的是容器图像对象留在缓存里不丢滚回去不用重新解码来回滚动内存保持平稳。关闭期窗口 Closing 时按顺序摘事件、清缓存这一节解决窗口关闭后对象图释放不掉的问题。典型的写法是在构造函数里订阅了事件、没有对应退订窗口关了处理器还挂在事件上缓存里还压着几十张图整棵对象树无法释放应用常驻系统托盘时内存就一直高位挂着。修法是把清理集中到OnClosing末尾顺序固定先摘事件再释放缓存最后调基类protected override void OnClosing(CancelEventArgs e) { _pageChanged - OnPageChanged; _imageCache.Dispose(); base.OnClosing(e); }框架内有现成参照。src/Wpf.Ui.Tray/ 里的NotifyIconService把托盘图标的生命周期挂在父窗口的Closing上收到事件即释放内部管理器SetParentWindow换窗口时先退订旧窗口再挂新窗口不漏也不重。src/Wpf.Ui/Win32/ 下的Utilities.SafeDispose也值得照抄其顺序取出引用、把字段置空、再调 Dispose既不怕二次 Dispose 报错也不漏释放。合入前自查五个问题过一遍✅ 有基线吗量过启动后私有内存并知道流程里涨的数从哪来 ✅ 图片按 URI 缓存并冻结了吗容量满时最久未访问的先出局 ✅ 大图解码在 UI 线程上吗解码调用包进Task.Run了吗 ✅ 长列表虚拟化了吗500 项会创建 500 个容器吗 ✅ 窗口关闭时先摘事件、再清缓存然后才释放其他 IDisposable 吗五项全过运行一段时间后内存会停在平台期哪一项不过哪一项就是持续上涨的元凶。想看完整用法参考 samples/Wpf.Ui.Demo.Mvvm/ 里的示例它用 wpfui 控件搭了三页导航结构可以直接对照本文的缓存与关闭期清理点检查自己的项目。【免费下载链接】wpfuiWPF UI provides the Fluent experience in your known and loved WPF framework. Intuitive design, themes, navigation and new immersive controls. All natively and effortlessly.项目地址: https://gitcode.com/GitHub_Trending/wp/wpfui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表