WPF中解决内存泄露的几点提示与解决方法
一直以來用WPF做一個項目,但是開發(fā)中途發(fā)現(xiàn)內(nèi)存開銷太大,用ANTS Memory Profiler分析時,發(fā)現(xiàn)在來回點幾次載入頁面的操作中,使得非托管內(nèi)存部分開銷從起始的43.59M一直到150M,而托管部分的開銷也一直持高不下,即每次申請的內(nèi)存在結(jié)束后不能完全釋放。在網(wǎng)上找了不少資料,甚受益,現(xiàn)在修改后,再也不會出現(xiàn)這種現(xiàn)象了(或者說,即使有也不嚇人),寫下幾個小心得:
1. 慎用WPF樣式模板合并
我發(fā)現(xiàn)不采用合并時,非托管內(nèi)存占用率較小,只是代碼的理解能力較差了,不過我們還有文檔大綱可以維護(hù)。
2. WPF樣式模板請共享
共享的方式最簡單不過的就是建立一個類庫項目,把樣式、圖片、筆刷什么的,都扔進(jìn)去,樣式引用最好使用StaticResource,開銷最小,但這樣就導(dǎo)致了一些寫作時的麻煩,即未定義樣式,就不能引用樣式,哪怕定義在后,引用在前都不行。
3. 慎用隱式類型var的弱引用
這個本來應(yīng)該感覺沒什么問題的,可是不明的是,在實踐中,發(fā)現(xiàn)大量采用var與老老實實的使用類型聲明的弱引用對比,總是產(chǎn)生一些不能正確回收的WeakRefrense(這點有待探討,因為開銷不是很大,可能存在一些手工編程的問題)
4. 寫一個接口約束一下
誰申請誰釋放,基本上這點能保證的話,內(nèi)存基本上就能釋放干凈了。我是這么做的:
interface IUIElement : IDisposable{
/// <summary>
/// 注冊事件
/// </summary>
void EventsRegistion();
/// <summary>
/// 解除事件注冊
/// </summary>
void EventDeregistration();
}
在實現(xiàn)上可以這樣:
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時,可以參考以上代碼,實現(xiàn)這樣接口,有利于資源釋放。
5. 定時回收垃圾
DispatcherTimer GCTimer = new DispatcherTimer();public MainWindow()
{
InitializeComponent();
this.GCTimer.Interval = TimeSpan.FromMinutes(10); //垃圾釋放定時器 我定為每十分鐘釋放一次,大家可根據(jù)需要修改 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. 較簡單或可循環(huán)平鋪的圖片用GeometryDrawing實現(xiàn)
一個圖片跟幾行代碼相比,哪個開銷更少肯定不用多說了,而且這幾行代碼還可以BaseOn進(jìn)行重用。
<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}"/>上面幾行代碼相當(dāng)于這個:
7. 使用Blend做樣式的時候,一定要檢查完成的代碼
眾所周知,Blend定義樣式時,產(chǎn)生的垃圾代碼還是比較多的,如果使用Blend,一定要檢查生成的代碼。
?
8. 靜態(tài)方法返回諸如List<>等變量的,請使用out
比如
public static List<String> myMothod(){...}
請改成
public static myMothod(out List<String> result){...}
?
9. 打針對此問題的微軟補丁
3.5的應(yīng)該都有了吧,這里附上NET4的內(nèi)存泄露補丁地址,下載點這里?(QFE:? Hotfix request to implement hotfix KB981107 in .NET 4.0?)
這是官方給的說明,看來在樣式和數(shù)據(jù)綁定部分下了點工夫啊:
繼續(xù)更新有關(guān)的三個8月補丁,詳細(xì)的請百度:KB2487367??KB2539634??KB2539636,都是NET4的補丁,在發(fā)布程序的時候,把這些補丁全給客戶安裝了會好的多。
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進(jìn)行多次更改時(循環(huán)賦值、連接之類的),使用StringBuilder。我已經(jīng)把工程里這種頻繁且大量改動string的操作全部換成了StringBuilder了,用ANTS Memory Profiler分析效果顯著,不僅提升了性能,而且垃圾也少了。
?
11. 其它用上的技術(shù)暫時還沒想到,再補充...
?
如果嚴(yán)格按以上操作進(jìn)行的話,可以得到一個滿意的結(jié)果:
運行了三十分鐘,不斷的切換功能,然后休息5分鐘,回頭一看,結(jié)果才17M左右內(nèi)存開銷,效果顯著吧。
然后對于調(diào)試信息的輸出,我的做法是在窗體應(yīng)用程序中附帶一個控制臺窗口,輸出調(diào)試信息,給一個類,方便大家:
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
{
//這里是改控制臺字體大小的,可能會導(dǎo)致異常,在我這個項目中我懶得弄了,如果需要的的話把注釋去掉就行了
//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進(jìn)行輸出就可以了。
轉(zhuǎn)載于:https://www.cnblogs.com/LastPropose/archive/2011/08/01/2124359.html
總結(jié)
以上是生活随笔為你收集整理的WPF中解决内存泄露的几点提示与解决方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 输卵管结扎复通手术怎么做?
- 下一篇: asp.net core 系列 6 MV