您好,登錄后才能下訂單哦!
項目中使用微軟RDLC生成工作票去打印,但是RDLC存在著嚴重的內存泄露問題。在生產高峰時期,工人將大量的工作票請求發送到服務器,隨著工作票的生成內存就一點點的被吃掉。致使IT部門不得不為一個小小的工作票服務準備一臺8G內存的服務器,并且定期的查看服務狀態。在生產高峰時期每小時都要重啟。
這個內存泄露問題自從VS2005以來就存在,微軟聲稱在2008 SP1中已經修正,但是項目中使用的是2010的程序集版本且問題依然很嚴重。從微軟官方的回復看由于RDLC使用VB進行表達式的計算,加載的VB相關的程序集由于某些原因不被Unload。我想微軟在VS2008SP1中修正的應該是這個問題,當然我沒有去考證,但是可以肯定的是在VS2010的RDLC中還是有內存泄露的代碼存在。
網友還做過測試,如果不使用Expression就不會導致內存泄露,但是我并不想修改太多的程序,如果你的項目剛剛開始或并不復雜,這也是一個辦法。參見原文:http://blog.darkthread.net/post-2012-01-12-rdlc-out-of-memory.aspx
于是著手從網上搜索如何查找內存泄露,推薦一個工具給大家。.Net Memory Profile http://memprofiler.com/. 這個工具可以分析出內存中哪些對象已經GC但是沒有被成功的移除或移除的不夠徹底。通過個這工具分析出LocalReport中的方法被事件或代理對象所引用無法GC。如下圖
從上圖可以看到LocalReport的身影,這張圖中的所有對象全與RDLC有關。
在網上搜到一牛人用反射解決了ReportViewer的內存泄露問題。參見原貼:http://social.msdn.microsoft.com/Forums/en-US/vsreportcontrols/thread/d21f8b56-3123-4aff-bf84-9cce727bc2ce
于是我參考了這個做法結合.Net Memory Profiler的分析結果開始將LocalReport對象上的事件和代理去掉。雖然這個方法失敗了還是把代碼貼出來吧,如下:
using System; using System.Reflection; using System.Linq; using System.Windows.Forms; using Microsoft.Reporting.WinForms; using Microsoft.Win32; using System.Collections; namespace TOG.ProductionOutput.Services { public class LocalReportDisposer : IDisposable { // Fields private bool _CollectGarbageOnDispose = false; private LocalReport localReport; private bool disposedValue = false; private const string LOCALREPORT_DATASOURCES = "m_dataSources"; private const string LOCALREPORT_PROCESSINGHOST = "m_processingHost"; private const string PROCESSINGHOST_DATARETRIEVAL = "m_dataRetrieval"; private const string DATARETRIEVAL_SUBREPORTDATACALLBACK = "m_subreportDataCallback"; private const string SUBREPORTDATACALLBACK_TARGET = "_target"; private const string PROCESSINGHOST_EXECUTIONSESSION = "m_executionSession"; private const string EXECUTIONSESSION_COMPILEDREPORT = "__compiledReport"; private const string EXECUTIONSESSION_REPORTSNAPSHOT = "__ReportSnapshot"; private const string DATASOURCES_ONCHANGE = "OnChange"; // Methods public LocalReportDisposer(LocalReport localReport) { if (localReport == null) { throw new ArgumentNullException("ReportViewer cannot be null."); } this.localReport = localReport; } public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!this.disposedValue && disposing) { //this.TearDownLocalReport(); this.localReport.Dispose(); if (this._CollectGarbageOnDispose) { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } } this.disposedValue = true; } private void TearDownLocalReport() { Type t = this.localReport.GetType(); //localReport.m_dataSources FieldInfo fi = t.GetField(LOCALREPORT_DATASOURCES, BindingFlags.NonPublic | BindingFlags.Instance); object dataSources = fi.GetValue(this.localReport); //remove event from localReport.m_dataSources.Change ReflectUtil.RemoveEventHandlersFrom( delegate(Delegate subject) { return subject.Method.Name == DATASOURCES_ONCHANGE; }, dataSources); //localReport.m_processingHost fi = t.GetField(LOCALREPORT_PROCESSINGHOST, BindingFlags.NonPublic | BindingFlags.Instance); object processingHost = fi.GetValue(this.localReport); //localReport.m_processingHost.dataretrieval t = processingHost.GetType().BaseType; fi = t.GetField(PROCESSINGHOST_DATARETRIEVAL, BindingFlags.NonPublic | BindingFlags.Instance); object dataRetrieval = fi.GetValue(processingHost); //localReport.m_processingHost.m_dataRetrieval.m_subreportDataCallback t = dataRetrieval.GetType(); fi = t.GetField(DATARETRIEVAL_SUBREPORTDATACALLBACK, BindingFlags.NonPublic | BindingFlags.Instance); object subReportDataCallBack = fi.GetValue(dataRetrieval); //localReport.m_processingHost.m_dataRetrieval.m_subreportDataCallback._target t = subReportDataCallBack.GetType().BaseType.BaseType; fi = t.GetField(SUBREPORTDATACALLBACK_TARGET, BindingFlags.NonPublic | BindingFlags.Instance); fi.SetValue(subReportDataCallBack, null); t = processingHost.GetType().BaseType; fi = t.GetField(PROCESSINGHOST_EXECUTIONSESSION, BindingFlags.NonPublic | BindingFlags.Instance); object executionSession = fi.GetValue(processingHost); t = executionSession.GetType(); fi = t.GetField(EXECUTIONSESSION_COMPILEDREPORT, BindingFlags.NonPublic | BindingFlags.Instance); IDisposable report = fi.GetValue(executionSession) as IDisposable; if (report != null) report.Dispose(); fi = t.GetField(EXECUTIONSESSION_REPORTSNAPSHOT, BindingFlags.NonPublic | BindingFlags.Instance); report = fi.GetValue(executionSession) as IDisposable; if (report != null) report.Dispose(); } // Properties public bool CollectGarbageOnDispose { get { return this._CollectGarbageOnDispose; } set { this._CollectGarbageOnDispose = value; } } } }
using System;using System.Reflection;using System.Linq;using System.Windows.Forms; using Microsoft.Reporting.WinForms; using Microsoft.Win32;using System.Collections;namespace TOG.ProductionOutput.Services{publicclass LocalReportDisposer : IDisposable {// Fields privatebool _CollectGarbageOnDispose = false;private LocalReport localReport;privatebool disposedValue = false;privateconststring LOCALREPORT_DATASOURCES = "m_dataSources";privateconststring LOCALREPORT_PROCESSINGHOST = "m_processingHost";privateconststring PROCESSINGHOST_DATARETRIEVAL = "m_dataRetrieval";privateconststring DATARETRIEVAL_SUBREPORTDATACALLBACK = "m_subreportDataCallback";privateconststring SUBREPORTDATACALLBACK_TARGET = "_target";privateconststring PROCESSINGHOST_EXECUTIONSESSION = "m_executionSession";privateconststring EXECUTIONSESSION_COMPILEDREPORT = "__compiledReport";privateconststring EXECUTIONSESSION_REPORTSNAPSHOT = "__ReportSnapshot";privateconststring DATASOURCES_ONCHANGE = "OnChange";// Methods public LocalReportDisposer(LocalReport localReport) {if (localReport == null) {thrownew ArgumentNullException("ReportViewer cannot be null."); }this.localReport = localReport; }publicvoid Dispose() {this.Dispose(true); GC.SuppressFinalize(this); }protectedvirtualvoid Dispose(bool disposing) {if (!this.disposedValue && disposing) {//this.TearDownLocalReport();this.localReport.Dispose();if (this._CollectGarbageOnDispose) { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } }this.disposedValue = true; }privatevoid TearDownLocalReport() { Type t = this.localReport.GetType();//localReport.m_dataSourcesFieldInfo fi = t.GetField(LOCALREPORT_DATASOURCES, BindingFlags.NonPublic | BindingFlags.Instance);object dataSources = fi.GetValue(this.localReport);//remove event from localReport.m_dataSources.ChangeReflectUtil.RemoveEventHandlersFrom(delegate(Delegate subject) { return subject.Method.Name == DATASOURCES_ONCHANGE; }, dataSources);//localReport.m_processingHostfi = t.GetField(LOCALREPORT_PROCESSINGHOST, BindingFlags.NonPublic | BindingFlags.Instance);object processingHost = fi.GetValue(this.localReport);//localReport.m_processingHost.dataretrievalt = processingHost.GetType().BaseType; fi = t.GetField(PROCESSINGHOST_DATARETRIEVAL, BindingFlags.NonPublic | BindingFlags.Instance);object dataRetrieval = fi.GetValue(processingHost);//localReport.m_processingHost.m_dataRetrieval.m_subreportDataCallbackt = dataRetrieval.GetType(); fi = t.GetField(DATARETRIEVAL_SUBREPORTDATACALLBACK, BindingFlags.NonPublic | BindingFlags.Instance);object subReportDataCallBack = fi.GetValue(dataRetrieval);//localReport.m_processingHost.m_dataRetrieval.m_subreportDataCallback._targett = subReportDataCallBack.GetType().BaseType.BaseType; fi = t.GetField(SUBREPORTDATACALLBACK_TARGET, BindingFlags.NonPublic | BindingFlags.Instance); fi.SetValue(subReportDataCallBack, null); t = processingHost.GetType().BaseType; fi = t.GetField(PROCESSINGHOST_EXECUTIONSESSION, BindingFlags.NonPublic | BindingFlags.Instance);object executionSession = fi.GetValue(processingHost); t = executionSession.GetType(); fi = t.GetField(EXECUTIONSESSION_COMPILEDREPORT, BindingFlags.NonPublic | BindingFlags.Instance); IDisposable report = fi.GetValue(executionSession) as IDisposable;if (report != null) report.Dispose(); fi = t.GetField(EXECUTIONSESSION_REPORTSNAPSHOT, BindingFlags.NonPublic | BindingFlags.Instance); report = fi.GetValue(executionSession) as IDisposable;if (report != null) report.Dispose(); }// Properties publicbool CollectGarbageOnDispose {get{returnthis._CollectGarbageOnDispose; }set{this._CollectGarbageOnDispose = value; } } }}
using System; using System.Collections.Generic; using System.Globalization; using System.Reflection; namespace TOG.ProductionOutput.Services { public sealed class ReflectUtil { private static BindingFlags PrivatePublicStaticInstance = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; public delegate bool MatchesOnDelegate(Delegate subject); public static void RemoveEventHandlersFrom( MatchesOnDelegate matchesOnDelegate, params object[] objectsWithEvents) { foreach (object owningObject in objectsWithEvents) { foreach (DelegateInfo eventFromOwningObject in GetDelegates(owningObject)) { foreach (Delegate subscriber in eventFromOwningObject.GetInvocationList()) { if (matchesOnDelegate(subscriber)) { EventInfo theEvent = eventFromOwningObject.GetEventInfo(); if(theEvent != null) RemoveSubscriberEvenIfItsPrivate(theEvent, owningObject, subscriber); } } } } } // You can use eventInfo.RemoveEventHandler(owningObject, subscriber) // unless it's a private delegate private static void RemoveSubscriberEvenIfItsPrivate( EventInfo eventInfo, object owningObject, Delegate subscriber) { MethodInfo privateRemoveMethod = eventInfo.GetRemoveMethod(true); privateRemoveMethod.Invoke(owningObject, PrivatePublicStaticInstance, null, new object[] { subscriber }, CultureInfo.CurrentCulture); } private static DelegateInfo[] GetDelegates(object owningObject) { List<DelegateInfo> delegates = new List<DelegateInfo>(); FieldInfo[] allPotentialEvents = owningObject.GetType() .GetFields(PrivatePublicStaticInstance); foreach (FieldInfo privateFieldInfo in allPotentialEvents) { Delegate eventFromOwningObject = privateFieldInfo.GetValue(owningObject) as Delegate; if (eventFromOwningObject != null) { delegates.Add(new DelegateInfo(eventFromOwningObject, privateFieldInfo, owningObject)); } } return delegates.ToArray(); } private class DelegateInfo { private readonly Delegate delegateInformation; public Delegate DelegateInformation { get { return delegateInformation; } } private readonly FieldInfo fieldInfo; private readonly object owningObject; public DelegateInfo(Delegate delegateInformation, FieldInfo fieldInfo, object owningObject) { this.delegateInformation = delegateInformation; this.fieldInfo = fieldInfo; this.owningObject = owningObject; } public Delegate[] GetInvocationList() { return delegateInformation.GetInvocationList(); } public EventInfo GetEventInfo() { return owningObject.GetType().GetEvent(fieldInfo.Name, PrivatePublicStaticInstance); } } } }
using System;using System.Collections.Generic;using System.Globalization;using System.Reflection;namespace TOG.ProductionOutput.Services{publicsealedclass ReflectUtil {privatestatic BindingFlags PrivatePublicStaticInstance = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;publicdelegatebool MatchesOnDelegate(Delegate subject);publicstaticvoid RemoveEventHandlersFrom( MatchesOnDelegate matchesOnDelegate, paramsobject[] objectsWithEvents) {foreach (object owningObject in objectsWithEvents) {foreach (DelegateInfo eventFromOwningObject in GetDelegates(owningObject)) {foreach (Delegate subscriber in eventFromOwningObject.GetInvocationList()) {if (matchesOnDelegate(subscriber)) { EventInfo theEvent = eventFromOwningObject.GetEventInfo();if(theEvent != null) RemoveSubscriberEvenIfItsPrivate(theEvent, owningObject, subscriber); } } } } }// You can use eventInfo.RemoveEventHandler(owningObject, subscriber) // unless it's a private delegateprivatestaticvoid RemoveSubscriberEvenIfItsPrivate( EventInfo eventInfo, object owningObject, Delegate subscriber) { MethodInfo privateRemoveMethod = eventInfo.GetRemoveMethod(true); privateRemoveMethod.Invoke(owningObject, PrivatePublicStaticInstance, null, newobject[] { subscriber }, CultureInfo.CurrentCulture); }privatestatic DelegateInfo[] GetDelegates(object owningObject) { List<DelegateInfo> delegates = new List<DelegateInfo>(); FieldInfo[] allPotentialEvents = owningObject.GetType() .GetFields(PrivatePublicStaticInstance);foreach (FieldInfo privateFieldInfo in allPotentialEvents) { Delegate eventFromOwningObject = privateFieldInfo.GetValue(owningObject) as Delegate;if (eventFromOwningObject != null) { delegates.Add(new DelegateInfo(eventFromOwningObject, privateFieldInfo, owningObject)); } }return delegates.ToArray(); }privateclass DelegateInfo {privatereadonly Delegate delegateInformation;public Delegate DelegateInformation {get { return delegateInformation; } } privatereadonly FieldInfo fieldInfo;privatereadonlyobject owningObject;public DelegateInfo(Delegate delegateInformation, FieldInfo fieldInfo, object owningObject) {this.delegateInformation = delegateInformation;this.fieldInfo = fieldInfo;this.owningObject = owningObject; }public Delegate[] GetInvocationList() {return delegateInformation.GetInvocationList(); }public EventInfo GetEventInfo() {return owningObject.GetType().GetEvent(fieldInfo.Name, PrivatePublicStaticInstance); } } }}
RefactUtil是我從網上找到的,因為使用RemoveEventHandler方法會報
“Cannot remove the event handler since no public remove method exists for the event.”,所以搜索到了這篇文章。
參見原文:http://www.thekua.com/atwork/2007/09/events-reflection-and-how-they-dont-work-in-c/comment-page-1/
由于復雜的引用關系并且在反射時并不是所有的分析器分析到的對象都能拿到,這個方法最終還是放棄了。
繼續在網上查找,找到了如下這個貼子
http://stackoverflow.com/questions/6220915/very-high-memory-usage-in-net-4-0
大致的意思就是起一個線程并讓Report運行在這個線程的內存堆上,這樣當線程銷毀時LocalReport也隨著線程一起銷毀了,試驗效果還是不錯的。
這時又在網上找到了另一段代碼http://www.pcreview.co.uk/forums/reportviewer-localreport-own-appdomain-t3997991.html。
大致的意思就是創建出一個新App Domain讓LocalReport運行在這個Domain上,當要銷毀LocalReport時銷毀這個App Domain即可。
最終代碼如下:
using Microsoft.Reporting.WinForms; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TOG.ProductionOutput.Services { /// <summary> /// Manager of LocalReport for create and dispose /// </summary> public class LocalReportManager : IDisposable { // Fields private bool collectGarbageOnDispose = false; private LocalReport localReport; private LocalReportFactory factory; private bool disposedValue = false; /// <summary> /// Init LocalReport Disposer /// </summary> /// <param name="localReport">LocalReport Object</param> public LocalReportManager() { factory = new LocalReportFactory(); this.localReport = factory.CreateLocalReportParser(); } /// <summary> /// get local report /// </summary> public LocalReport LocalReport { get { return localReport; } } /// <summary> /// IDispose.Dispose /// </summary> public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Dispose LocalReport Object /// </summary> /// <param name="disposing">GC?</param> protected virtual void Dispose(bool disposing) { if (!this.disposedValue && disposing) { factory.Unload(); if (this.collectGarbageOnDispose) { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } } this.disposedValue = true; } /// <summary> /// whether GC immediatelly when dispose, might affect the performance /// </summary> public bool CollectGarbageOnDispose { get { return this.collectGarbageOnDispose; } set { this.collectGarbageOnDispose = value; } } } }
using Microsoft.Reporting.WinForms;using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace TOG.ProductionOutput.Services{/// <summary>/// Manager of LocalReport for create and dispose/// </summary>publicclass LocalReportManager : IDisposable {// Fields privatebool collectGarbageOnDispose = false;private LocalReport localReport;private LocalReportFactory factory;privatebool disposedValue = false;/// <summary>/// Init LocalReport Disposer/// </summary>/// <param name="localReport">LocalReport Object</param>public LocalReportManager() { factory = new LocalReportFactory();this.localReport = factory.CreateLocalReportParser(); }/// <summary>/// get local report/// </summary>public LocalReport LocalReport {get { return localReport; } }/// <summary>/// IDispose.Dispose/// </summary>publicvoid Dispose() {this.Dispose(true); GC.SuppressFinalize(this); }/// <summary>/// Dispose LocalReport Object/// </summary>/// <param name="disposing">GC?</param>protectedvirtualvoid Dispose(bool disposing) {if (!this.disposedValue && disposing) { factory.Unload();if (this.collectGarbageOnDispose) { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } }this.disposedValue = true; }/// <summary>/// whether GC immediatelly when dispose, might affect the performance/// </summary>publicbool CollectGarbageOnDispose {get{returnthis.collectGarbageOnDispose; }set{this.collectGarbageOnDispose = value; } } }}
using Microsoft.Reporting.WinForms; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; namespace TOG.ProductionOutput.Services { /// <summary> /// Factory of LocalReport /// </summary> public class LocalReportFactory : MarshalByRefObject { public AppDomain LocalAppDomain = null; public string ErrorMessage = string.Empty; /// <summary> /// Creates a new instance of the LocalReportParser in a new AppDomain /// </summary> /// <returns></returns> public LocalReport CreateLocalReportParser() { this.CreateAppDomain(null); LocalReport parser = null; try { Type MyLR = typeof(LocalReport); parser = (LocalReport)this.LocalAppDomain.CreateInstanceAndUnwrap(MyLR.Assembly.FullName, MyLR.FullName); } catch (Exception ex) { this.ErrorMessage = ex.Message; } return parser; } /// <summary> /// Create a new app domain. /// </summary> /// <param name="appDomain">domain name</param> /// <returns></returns> private void CreateAppDomain(string appDomain) { if (string.IsNullOrEmpty(appDomain)) appDomain = "LocalReportParser" + Guid.NewGuid().ToString().GetHashCode().ToString("x"); AppDomainSetup domainSetup = new AppDomainSetup(); // *** Point at current directory domainSetup.DisallowBindingRedirects = false; domainSetup.DisallowCodeDownload = true; this.LocalAppDomain = AppDomain.CreateDomain(appDomain, null, domainSetup); // *** Need a custom resolver so we can load assembly from non current path this.LocalAppDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); } Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { foreach (Assembly LR in AppDomain.CurrentDomain.GetAssemblies()) { if (string.Compare(LR.GetName().Name, args.Name, true) == 0 || string.Compare(LR.FullName, args.Name, true) == 0) return LR; } return null; } /// <summary> /// Unload app domain /// </summary> public void Unload() { if (this.LocalAppDomain != null) { AppDomain.Unload(this.LocalAppDomain); this.LocalAppDomain = null; } } } }
使用方法如下:
using (LocalReportManager reportManager = new LocalReportManager()) { LocalReport Ticket = reportManager.LocalReport; // to do report }
config中加入:
<runtime> <NetFx40_LegacySecurityPolicy enabled="true"/> </runtime>
如果不加入這個配置還是不行,我想LocalReport雖然放到了當前的Domain里,但是LocalReport在Render時不一定會運行在當前的Domain里。微軟的官方說法是默認是當前Domain,但是我的測試結果是不加入這個配置就不會運行在當前Domain里。
分析結果中就看不到LocalReport及其同黨的身影了。
這僅是一個臨時的解決方案,記錄一下僅供參考,希望對你有所幫助,如果有更好的辦法請告訴我,謝謝。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。