一直以来用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 )

这是官方给的说明,看来在样式和数据绑定部分下了点工夫啊:

  1. 运行一个包含样式或模板,请参阅通过使用 StaticResource 标记扩展或 DynamicResource 标记扩展应用程序资源的 WPF 应用程序。 创建使用这些样式或模板的多个控件。 但是,这些控件不使用引用的资源。 在这种情况的一些内存WeakReference对象和空间泄漏的控股数组后,垃圾回收释放该控件。
  2. 运行一个包含的控件的属性是数据绑定到的 WPF 应用程序DependencyObject对象。 该对象的生存期是超过控件的生存期。 许多控件时创建,一些内存WeakReference对象和容纳数组空格被泄漏后垃圾回收释放该控件。
  3. 运行使用树视图控件或控件派生于的 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中解决内存泄露的几点提示与解决方法相关推荐

  1. OpenCV中的内存泄露问题(cvLoadImage,cvCloneImage)

    转自:http://apps.hi.baidu.com/share/detail/30893646 在做项目的过程中,使用OpenCV经常会出现一些内存泄露问题,自己编写的程序出现问题还情有可原,但若 ...

  2. C/C++内存泄露,如何解决内存泄露?

    参考文章:vs2008如何查看内存空间在栈上还是堆上_C++ 如何避免内存泄露(文章从头到尾看了一遍,讲了很多方法,很多工具,比如智能指针SmartX之类,但是印象最深的还是在不使用工具的情况下,使用 ...

  3. java 解决内存泄露_Java内存泄露的理解与解决

    Java内存管理机制 在C++语言中,如果需要动态分配一块内存,程序员需要负责这块内存的整个生命周期.从申请分配.到使用.再到最后的释放.这样的过程非常灵活,但是却十分繁琐,程序员很容易由于疏忽而忘记 ...

  4. OpenCV中的内存泄露问题(cvLoadImage,cvCloneImage)【转】

    在做项目的过程中,使用OpenCV经常会出现一些内存泄露问题,自己编写的程序出现问题还情有可原,但若是库函数调用和使用时出现,却很令我恼火.花了好长时间和实践的经验告诉我应该客服它.下面把一些检测出的 ...

  5. C++11 解决内存泄露问题的智能指针:shared_ptr、unique_ptr、weak_ptr

    我们经常听到内存泄漏,但是对这个抽象的概念一直没有什么理解,比如产生内存泄漏又将如何,我平时写程序从来不考虑这个等等.这篇的目的:第一,给大家实验实验内存泄露带来的问题,让大家直观感受内存泄露.第二, ...

  6. 乐鑫esp8266学习rtos3.0笔记第5篇:基于乐鑫idf框架,研究出超稳定、掉线重连、解决内存泄露问题的Mqtt框架,支持esp8266和esp32!(附带链接)

    本系列博客学习由非官方人员 半颗心脏 潜心所力所写,不做开发板.仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 1. Esp8266之 搭建开发环境,开始一个" ...

  7. vs调试c语言检查内存泄露,VisualStudio中检查内存泄露方法

    项目工程中存在内存泄露,被折磨了一晚上,终于查了出来,因为之前没有相关的经验,还比较生疏,在此记录下来,方便以后查找. 对于malloc出的内存的检测方法 这篇文章中详细地记录了从检查到找到确定位置到 ...

  8. LeakCanary——消除Android中的内存泄露

    2019独角兽企业重金招聘Python工程师标准>>> ##LeakCanary ####简介 LeakCanary是Square公司最近公布的开源项目,旨在消除Android中的内 ...

  9. troubleshoot之:使用JFR解决内存泄露

    文章目录 简介 一个内存泄露的例子 使用JFR和JMC来分析内存泄露 OldObjectSample 总结 简介 虽然java有自动化的GC,但是还会有内存泄露的情况.当然java中的内存泄露跟C++ ...

最新文章

  1. LINQ技术、EF技术都出来蛮久了,软件开发者、软件公司是否还有必要有自己的代码生成器?...
  2. tensorflow基础
  3. Git——git push 错误[error: src refspec master does not match any]解决方案
  4. python语言pos_Python自然语言处理(二)--NLTK调用Stanford_NLP_Tools完成NLP任务
  5. CSDN”好师父“培养高素质技术人才
  6. Spring Cloud Stream
  7. 计算机应用与篮球有关的文章,浅析计算机技术应用对高校篮球教学的作用与影响...
  8. STM32学习笔记(6):PWM控制
  9. typescript中的类型type与接口interface
  10. CSP-S 2020
  11. php、git、redis函数合集
  12. 【数据可视化应用】绘制森林图(附Python和R语言代码)
  13. 正态分布均值μ的贝叶斯Bayes估计推导
  14. 【白娘子传奇】大话版VM一键端+GM后台+视频教程
  15. 一个好的WordPress主机应具备的10个特征
  16. 屏幕点击功能产品实现
  17. IBM服务器修改时间为24小时制,产品国际化——日期和时间格式(二)
  18. 用计算机修图属于,修图电脑之显示篇
  19. YOLOv5中wandb下载及使用
  20. 抛硬币第一次出现连续两个正面的期望次数

热门文章

  1. c语言实训作业总结,c语言程序设计上机实践心得报告
  2. 安装mysql5.7.24rpm_centos7安装mysql-5.7.24(rpm安装)
  3. matlab多项式加法运算,matlab多项式运算与代数方程求解解析.ppt
  4. python文件管理包_Python标准库04 文件管理 (部分os包,shutil包)
  5. python 微信支付接口 详解_Python支付接口汇总大全(包含微信、支付宝等,长期更新、欢迎补充)...
  6. armv7 cortex a系列编程手册_AWTK能为现代GUI编程带来何种改变?
  7. 力扣删除排序数组中的重复项 II
  8. scala中何时使用下划线_在Scala中使用下划线
  9. asinh函数_JavaScript中带有示例的Math.asinh()方法
  10. 远控免杀专题1---基础篇