WPF中解决内存泄露的几点提示与解决方法
一直以来用WPF做一个项目,但是开发中途发现内存开销太大,用ANTS Memory Profiler分析时,发现在来回点几次载入页面的操作中,使得非托管内存部分开销从起始的43.59M一直到150M,而托管部分的开销也一直持高不下,即每次申请的内存在结束后不能完全释放。在网上找了不少资料,甚受益,现在修改后,再也不会出现这种现象了(或者说,即使有也不吓人),写下几个小心得:
1. 慎用WPF样式模板合并
我发现不采用合并时,非托管内存占用率较小,只是代码的理解能力较差了,不过我们还有文档大纲可以维护。
2. WPF样式模板请共享
共享的方式最简单不过的就是建立一个类库项目,把样式、图片、笔刷什么的,都扔进去,样式引用最好使用StaticResource,开销最小,但这样就导致了一些写作时的麻烦,即未定义样式,就不能引用样式,哪怕定义在后,引用在前都不行。
3. 慎用隐式类型var的弱引用
这个本来应该感觉没什么问题的,可是不明的是,在实践中,发现大量采用var与老老实实的使用类型声明的弱引用对比,总是产生一些不能正确回收的WeakRefrense(这点有待探讨,因为开销不是很大,可能存在一些手工编程的问题)
4. 写一个接口约束一下
谁申请谁释放,基本上这点能保证的话,内存基本上就能释放干净了。我是这么做的:
interface IUIElement : IDisposable {/// <summary>/// 注册事件/// </summary> void EventsRegistion(); /// <summary>/// 解除事件注册/// </summary> void EventDeregistration(); }
在实现上可以这样:
1 #region IUIElement 成员 2 public void EventsRegistion() 3 { 4 this.traineeReport.SelectionChanged += new SelectionChangedEventHandler(traineeReport_SelectionChanged); 5 } 6 7 public void EventDeregistration() 8 { 9 this.traineeReport.SelectionChanged -= new SelectionChangedEventHandler(traineeReport_SelectionChanged);10 }11 12 private bool disposed;13 14 ~TraineePaymentMgr()15 {16 ConsoleEx.Log("{0}被销毁", this);17 Dispose(false);18 }19 20 public void Dispose()21 {22 ConsoleEx.Log("{0}被手动销毁", this);23 Dispose(true);24 GC.SuppressFinalize(this);25 }26 27 protected void Dispose(bool disposing)28 {29 ConsoleEx.Log("{0}被自动销毁", this);30 if(!disposed)31 {32 if(disposing)33 {34 //托管资源释放35 ((IDisposable)traineeReport).Dispose();36 ((IDisposable)traineePayment).Dispose();37 }38 //非托管资源释放39 }40 disposed = true;41 }42 #endregion
比如写一个UserControl或是一个Page时,可以参考以上代码,实现这样接口,有利于资源释放。
5. 定时回收垃圾
DispatcherTimer GCTimer = new DispatcherTimer();public MainWindow(){ InitializeComponent();this.GCTimer.Interval = TimeSpan.FromMinutes(10); //垃圾释放定时器 我定为每十分钟释放一次,大家可根据需要修改
this.GCTimer.start(); this.EventsRegistion(); // 注册事件} public void EventsRegistion(){this.GCTimer.Tick += new EventHandler(OnGarbageCollection);} public void EventDeregistration(){this.GCTimer.Tick -= new EventHandler(OnGarbageCollection);} void OnGarbageCollection(object sender, EventArgs e){ GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();}
6. 较简单或可循环平铺的图片用GeometryDrawing实现
一个图片跟几行代码相比,哪个开销更少肯定不用多说了,而且这几行代码还可以BaseOn进行重用。
<DrawingGroup x:Key="Diagonal_50px"><DrawingGroup.Children><GeometryDrawing Brush="#FF2A2A2A" Geometry="F1 M 0,0L 50,0L 50,50L 0,50 Z"/><GeometryDrawing Brush="#FF262626" Geometry="F1 M 50,0L 0,50L 0,25L 25,0L 50,0 Z"/><GeometryDrawing Brush="#FF262626" Geometry="F1 M 50,25L 50,50L 25,50L 50,25 Z"/></DrawingGroup.Children></DrawingGroup>
这边是重用
<DrawingBrush x:Key="FrameListMenuArea_Brush" Stretch="Fill" TileMode="Tile" Viewport="0,0,50,50" ViewportUnits="Absolute"
Drawing="{StaticResource Diagonal_50px}"/>
上面几行代码相当于这个:
7. 使用Blend做样式的时候,一定要检查完成的代码
众所周知,Blend定义样式时,产生的垃圾代码还是比较多的,如果使用Blend,一定要检查生成的代码。
8. 静态方法返回诸如List<>等变量的,请使用out
比如
public static List<String> myMothod() {...}
请改成
public static myMothod(out List<String> result) {...}
9. 打针对此问题的微软补丁
3.5的应该都有了吧,这里附上NET4的内存泄露补丁地址,下载点这里 (QFE: Hotfix request to implement hotfix KB981107 in .NET 4.0 )
这是官方给的说明,看来在样式和数据绑定部分下了点工夫啊:
- 运行一个包含样式或模板,请参阅通过使用 StaticResource 标记扩展或 DynamicResource 标记扩展应用程序资源的 WPF 应用程序。 创建使用这些样式或模板的多个控件。 但是,这些控件不使用引用的资源。 在这种情况的一些内存WeakReference对象和空间泄漏的控股数组后,垃圾回收释放该控件。
- 运行一个包含的控件的属性是数据绑定到的 WPF 应用程序DependencyObject对象。 该对象的生存期是超过控件的生存期。 许多控件时创建,一些内存WeakReference对象和容纳数组空格被泄漏后垃圾回收释放该控件。
- 运行使用树视图控件或控件派生于的 WPF 应用程序,选择器类。 将控件注册为控制中的键盘焦点的内部通知在KeyboardNavigation类。 该应用程序创建这些控件的很多。 例如对于您添加并删除这些控件。 在本例中为某些内存WeakReference对象和容纳数组空格被泄漏后垃圾回收释放该控件。
继续更新有关的三个8月补丁,详细的请百度:KB2487367 KB2539634 KB2539636,都是NET4的补丁,在发布程序的时候,把这些补丁全给客户安装了会好的多。
10. 对string怎么使用的建议
这个要解释话就长了,下面仅给个例子说明一下,具体的大家去找找MSDN
string ConcatString(params string[] items) {string result = "";foreach (string item in items) { result += item; }return result; } string ConcatString2(params string[] items) { StringBuilder result = new StringBuilder();for(int i=0, count = items.Count(); i<count; i++) { result.Append(items[i]); }return result.ToString(); }
建议在需要对string进行多次更改时(循环赋值、连接之类的),使用StringBuilder。我已经把工程里这种频繁且大量改动string的操作全部换成了StringBuilder了,用ANTS Memory Profiler分析效果显著,不仅提升了性能,而且垃圾也少了。
11. 其它用上的技术暂时还没想到,再补充...
如果严格按以上操作进行的话,可以得到一个满意的结果:
运行了三十分钟,不断的切换功能,然后休息5分钟,回头一看,结果才17M左右内存开销,效果显著吧。
然后对于调试信息的输出,我的做法是在窗体应用程序中附带一个控制台窗口,输出调试信息,给一个类,方便大家:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.InteropServices; namespace Trainee.UI.UIHelper{public struct COORD {public ushort X;public ushort Y; }; public struct CONSOLE_FONT {public uint index;public COORD dim; }; public static class ConsoleEx { [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("kernel32", CharSet = CharSet.Auto)]internal static extern bool AllocConsole(); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("kernel32", CharSet = CharSet.Auto)]internal static extern bool SetConsoleFont(IntPtr consoleFont, uint index); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("kernel32", CharSet = CharSet.Auto)]internal static extern bool GetConsoleFontInfo(IntPtr hOutput, byte bMaximize, uint count, [In, Out] CONSOLE_FONT[] consoleFont); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("kernel32", CharSet = CharSet.Auto)]internal static extern uint GetNumberOfConsoleFonts(); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("kernel32", CharSet = CharSet.Auto)]internal static extern COORD GetConsoleFontSize(IntPtr HANDLE, uint DWORD); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("kernel32.dll ")]internal static extern IntPtr GetStdHandle(int nStdHandle); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]internal static extern int GetConsoleTitle(String sb, int capacity); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("user32.dll", EntryPoint = "UpdateWindow")]internal static extern int UpdateWindow(IntPtr hwnd); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("user32.dll")]internal static extern IntPtr FindWindow(String sClassName, String sAppName); public static void OpenConsole() { var consoleTitle = "> Debug Console"; AllocConsole(); Console.BackgroundColor = ConsoleColor.Black; Console.ForegroundColor = ConsoleColor.Cyan; Console.WindowWidth = 80; Console.CursorVisible = false; Console.Title = consoleTitle; Console.WriteLine("DEBUG CONSOLE WAIT OUTPUTING...{0} {1}\n", DateTime.Now.ToLongTimeString()); try {//这里是改控制台字体大小的,可能会导致异常,在我这个项目中我懒得弄了,如果需要的的话把注释去掉就行了//IntPtr hwnd = FindWindow(null, consoleTitle);//IntPtr hOut = GetStdHandle(-11); //const uint MAX_FONTS = 40;//uint num_fonts = GetNumberOfConsoleFonts();//if (num_fonts > MAX_FONTS) num_fonts = MAX_FONTS;//CONSOLE_FONT[] fonts = new CONSOLE_FONT[MAX_FONTS];//GetConsoleFontInfo(hOut, 0, num_fonts, fonts);//for (var n = 7; n < num_fonts; ++n)//{// //fonts[n].dim = GetConsoleFontSize(hOut, fonts[n].index);// //if (fonts[n].dim.X == 106 && fonts[n].dim.Y == 33)// //{// SetConsoleFont(hOut, fonts[n].index);// UpdateWindow(hwnd);// return;// //}//} }catch { } } public static void Log(String format, params object[] args) { Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] " + format, args); }public static void Log(Object arg) { Console.WriteLine(arg); } }}
在程序启动时,可以用ConsoleEx.OpenConsole()打开控制台,用ConsoleEx.Log(.....)或者干脆用Console.WriteLine进行输出就可以了。
转载于:https://www.cnblogs.com/LastPropose/archive/2011/08/01/2124359.html
WPF中解决内存泄露的几点提示与解决方法相关推荐
- OpenCV中的内存泄露问题(cvLoadImage,cvCloneImage)
转自:http://apps.hi.baidu.com/share/detail/30893646 在做项目的过程中,使用OpenCV经常会出现一些内存泄露问题,自己编写的程序出现问题还情有可原,但若 ...
- C/C++内存泄露,如何解决内存泄露?
参考文章:vs2008如何查看内存空间在栈上还是堆上_C++ 如何避免内存泄露(文章从头到尾看了一遍,讲了很多方法,很多工具,比如智能指针SmartX之类,但是印象最深的还是在不使用工具的情况下,使用 ...
- java 解决内存泄露_Java内存泄露的理解与解决
Java内存管理机制 在C++语言中,如果需要动态分配一块内存,程序员需要负责这块内存的整个生命周期.从申请分配.到使用.再到最后的释放.这样的过程非常灵活,但是却十分繁琐,程序员很容易由于疏忽而忘记 ...
- OpenCV中的内存泄露问题(cvLoadImage,cvCloneImage)【转】
在做项目的过程中,使用OpenCV经常会出现一些内存泄露问题,自己编写的程序出现问题还情有可原,但若是库函数调用和使用时出现,却很令我恼火.花了好长时间和实践的经验告诉我应该客服它.下面把一些检测出的 ...
- C++11 解决内存泄露问题的智能指针:shared_ptr、unique_ptr、weak_ptr
我们经常听到内存泄漏,但是对这个抽象的概念一直没有什么理解,比如产生内存泄漏又将如何,我平时写程序从来不考虑这个等等.这篇的目的:第一,给大家实验实验内存泄露带来的问题,让大家直观感受内存泄露.第二, ...
- 乐鑫esp8266学习rtos3.0笔记第5篇:基于乐鑫idf框架,研究出超稳定、掉线重连、解决内存泄露问题的Mqtt框架,支持esp8266和esp32!(附带链接)
本系列博客学习由非官方人员 半颗心脏 潜心所力所写,不做开发板.仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 1. Esp8266之 搭建开发环境,开始一个" ...
- vs调试c语言检查内存泄露,VisualStudio中检查内存泄露方法
项目工程中存在内存泄露,被折磨了一晚上,终于查了出来,因为之前没有相关的经验,还比较生疏,在此记录下来,方便以后查找. 对于malloc出的内存的检测方法 这篇文章中详细地记录了从检查到找到确定位置到 ...
- LeakCanary——消除Android中的内存泄露
2019独角兽企业重金招聘Python工程师标准>>> ##LeakCanary ####简介 LeakCanary是Square公司最近公布的开源项目,旨在消除Android中的内 ...
- troubleshoot之:使用JFR解决内存泄露
文章目录 简介 一个内存泄露的例子 使用JFR和JMC来分析内存泄露 OldObjectSample 总结 简介 虽然java有自动化的GC,但是还会有内存泄露的情况.当然java中的内存泄露跟C++ ...
最新文章
- LINQ技术、EF技术都出来蛮久了,软件开发者、软件公司是否还有必要有自己的代码生成器?...
- tensorflow基础
- Git——git push 错误[error: src refspec master does not match any]解决方案
- python语言pos_Python自然语言处理(二)--NLTK调用Stanford_NLP_Tools完成NLP任务
- CSDN”好师父“培养高素质技术人才
- Spring Cloud Stream
- 计算机应用与篮球有关的文章,浅析计算机技术应用对高校篮球教学的作用与影响...
- STM32学习笔记(6):PWM控制
- typescript中的类型type与接口interface
- CSP-S 2020
- php、git、redis函数合集
- 【数据可视化应用】绘制森林图(附Python和R语言代码)
- 正态分布均值μ的贝叶斯Bayes估计推导
- 【白娘子传奇】大话版VM一键端+GM后台+视频教程
- 一个好的WordPress主机应具备的10个特征
- 屏幕点击功能产品实现
- IBM服务器修改时间为24小时制,产品国际化——日期和时间格式(二)
- 用计算机修图属于,修图电脑之显示篇
- YOLOv5中wandb下载及使用
- 抛硬币第一次出现连续两个正面的期望次数
热门文章
- c语言实训作业总结,c语言程序设计上机实践心得报告
- 安装mysql5.7.24rpm_centos7安装mysql-5.7.24(rpm安装)
- matlab多项式加法运算,matlab多项式运算与代数方程求解解析.ppt
- python文件管理包_Python标准库04 文件管理 (部分os包,shutil包)
- python 微信支付接口 详解_Python支付接口汇总大全(包含微信、支付宝等,长期更新、欢迎补充)...
- armv7 cortex a系列编程手册_AWTK能为现代GUI编程带来何种改变?
- 力扣删除排序数组中的重复项 II
- scala中何时使用下划线_在Scala中使用下划线
- asinh函数_JavaScript中带有示例的Math.asinh()方法
- 远控免杀专题1---基础篇