none
Possible memory leak with ReportViewer.RefreshReport() RRS feed

  • Question

  • I'm creating a VB.NET front-end application for viewing reports in local mode.  I noticed that ReportViewer.RefreshReport() seems to be generating a memory leak by observing my application's memory with Windows Task Manager.  It seems successive calls to this method cause the memory to increase.  This memory is not released when I close the report.  Is anyone else having this issue?  If so, how did you solve it?

     

    Saturday, February 16, 2008 1:01 AM

Answers

  • There are actually two problems here.  Running a report requires the report processing engine to generate and load an assembly in order to evaluate expressions in the report.  By default in local mode, this expression host assembly is loaded into the main appdomain.  Since assemblies can't be individually unloaded, it remains loaded permanently.

     

    Local mode does have the option to run expressions in a sandboxed appdomain (ReportViewer.LocalReport.ExecuteReportInSandboxAppDomain()).  However, there is currently a bug that this appdomain does not get unloaded at the appropriate time.  We are currently working on a fix for this and hope to get this into VS 2008 SP1.  If you need a fix sooner, customer support may be able to provide additional options: http://support.microsoft.com/contactussupport

     

    Thursday, February 21, 2008 2:29 AM
    Moderator

All replies

  • There are actually two problems here.  Running a report requires the report processing engine to generate and load an assembly in order to evaluate expressions in the report.  By default in local mode, this expression host assembly is loaded into the main appdomain.  Since assemblies can't be individually unloaded, it remains loaded permanently.

     

    Local mode does have the option to run expressions in a sandboxed appdomain (ReportViewer.LocalReport.ExecuteReportInSandboxAppDomain()).  However, there is currently a bug that this appdomain does not get unloaded at the appropriate time.  We are currently working on a fix for this and hope to get this into VS 2008 SP1.  If you need a fix sooner, customer support may be able to provide additional options: http://support.microsoft.com/contactussupport

     

    Thursday, February 21, 2008 2:29 AM
    Moderator
  • Brian, thanks for the info.  This was very helpful.

     

    To anyone else who is interested, here are some other threads on this issue:

     

    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=449474&SiteID=1
    http://www.scitech.se/blog/index.php/2007/10/05/memory-leak-in-toolstriptextboxcontrol/#comment-1295
    https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=305199

     

    Also, note that monitoring memory usage with Windows Task Manager can be misleading (the Mem Usage column just shows the working set).  It's better to use other utilities like perfmon.exe or WinDbg in conjunction with Task Manager.
    Thursday, February 21, 2008 5:58 PM
  • The information on those other threads is correct, but that's the same issue I descibed.  The event handler problem was causing the report viewer to keep the entire report snapshot around.  This includes the compiled report definition as well as some of the report data.  That's certainly much larger than the expression host assembly I mentioned.  This issue (as it pertains to the ReportViewer) was fixed in VS 2008.

    Friday, February 22, 2008 11:51 PM
    Moderator
  • Hello Brian,

     

    I downloaded the Visual studio 2008 trial version and checked out the ReportViewer Control. I see that the memory leak with ReportViewer is still very much there.I used a third party profiling tool (YourKit) to check the memory. The ReportToolBar is one reason why the ReportViewer reference is not GCed. There are also a bunch of other EventHandler classes (such as searchEventHandler, ExportEventHandler ,ZoomChangedEventHandler etc) which are holding references to the ReportViewer. This control is so cool but for the memory issue.

    Was there any update that I have to install after I install VS 2008 ? Any info. on this would be very helpful..

     

    To see the memory leak, all we need to do is to invoke a WinForm with a ReportViewer Control on it. We dont need to add datasources etc. Just opening and closing this Form from another Form will be enough to see the leaks.

     

    thanks

     

    Tuesday, March 11, 2008 8:24 PM
  • As far as I know, these issues were fixed in VS 2008, so I'd like to take a closer look at your project.  Will you file a bug at http://connect.microsoft.com and attach the project you are using?  That's probably the easiest way to get it into our tracking database.

    Thursday, March 13, 2008 11:20 PM
    Moderator
  • Thanks for your response.

    I have logged the defect and the project as an attachment in the microsoft connect website.

    Monday, March 17, 2008 2:57 PM
  • I observed the same problem using, in a batch process, iteratively the method ReportViewer.Render.

    This causes a very problematic leak, my intention was to use ReportViewer to produce a lot of PDF documents (invoices) (> 5000 or more) and this leak forces me to stop-restart this batch process.

    In other answers you say that the fix for this problem will come in a Microsoft ReportViewer for VS 2008 SP.

    Is it possible to have a fix for this for .NET 2.0 (that is, with Microsoft ReportViewer for VS 2005)? 

    In my production systems for now I have .NET 2.0 Runtime installed and for now I have no time and plan to pass to .NET 3.5 to run the fix. 

    For now I haven't a plan to use Microsoft ReportViewer for VS 2008 (from Microsoft site, the Redistribuitable package  requires to install the .NET 3.5 runtime) and .NET 3.5 runtime. 

    Then, is it possible to have a fix for this problem that runs on .NET 2.0 Runtime? 

     

    • Proposed as answer by Randy Lee1 Wednesday, June 4, 2008 11:37 PM
    Thursday, March 20, 2008 12:14 PM
  • Here is the "real" fix - a hak, but it works...

    The problem is not the ReportViewer at all, but is actually the ToolStrip.  I created a "helper" class that can be used with the ReportViewer to unhook the orphan delegates in the ReportToolStrip's "Find" and "Page" ToolStripTextBoxes via reflection.  This has been tested with 2.0 and 3.5 frameworks (both have the same problem).  Beware, this hak could break if Microsoft decides to fix the problem...

    Shows a how to use on the form.  Control containers should call TearDownReportViewer prior to Control.Dispose.

    How to use on a form containing a ReportViewer.  The _FixIt bool is just to be able to see the difference between fixed and not fixed:
    Public Class FormReport  
     
        Private _FixIt As Boolean = False 
        Private _ReportViewerDisposer As ReportViewerDisposer  
     
        Public Property FixIt() As Boolean 
            Get 
                Return Me._FixIt  
            End Get 
            Set(ByVal value As Boolean)  
                Me._FixIt = value  
            End Set 
        End Property 
     
        Private Sub FormReport_Load(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles MyBase.Load  
            If Me._FixIt Then 
                Me._ReportViewerDisposer = New ReportViewerDisposer(Me.ReportViewer1)  
                Me._ReportViewerDisposer.SetUpReportViewer()  
            End If 
            Me.ReportViewer1.RefreshReport()  
        End Sub 
     
        Private Sub FormReport_Deactivate(ByVal sender As ObjectByVal e As System.EventArgs) Handles Me.Deactivate  
            If Me._FixIt Then 
                Me._ReportViewerDisposer.CollectGarbageOnDispose = True 
                Me._ReportViewerDisposer.TearDownReportViewer()  
                Me._ReportViewerDisposer.Dispose()  
            End If 
        End Sub 
     
    End Class 

     The Class

    Public Class ReportViewerDisposer  
        Implements IDisposable  
     
        Private _ReportViewer As Microsoft.Reporting.WinForms.ReportViewer  
        Private Const TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE As String = "currentPage" 
        Private Const TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND As String = "textToFind" 
        Private Const TOOLSTRIP_CONTROL_NAME As String = "reportToolBar" 
        Private Const EVENTHANDLER_ON_USER_PREFERENCE_CHANGED As String = "OnUserPreferenceChanged" 
        Private _CollectGarbageOnDispose As Boolean = False 
        'start with a good buffer to insure removal of all delegates  
        Private _BufferCount As Integer = 20  
        Private _CurrentPageVisibleChangedCounter As Integer = 0  
        Private _TextToFindVisibleChangedCounter As Integer = 0  
     
        Public Property BufferCount() As Integer 
            Get 
                Return Me._BufferCount  
            End Get 
            Set(ByVal value As Integer)  
                Me._BufferCount = value  
            End Set 
        End Property 
     
        Public Property CollectGarbageOnDispose()  
            Get 
                Return Me._CollectGarbageOnDispose  
            End Get 
            Set(ByVal value)  
                Me._CollectGarbageOnDispose = value  
            End Set 
        End Property 
     
        Public Sub New(ByVal rptv As Microsoft.Reporting.WinForms.ReportViewer)  
            If rptv Is Nothing Then 
                Throw New ArgumentNullException("ReportViewer cannot be null.")  
            End If 
            Me._ReportViewer = rptv  
        End Sub 
     
        Public Sub SetUpReportViewer()  
            Dim fi As Reflection.FieldInfo = _ReportViewer.GetType().GetField(TOOLSTRIP_CONTROL_NAME, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                'reportToolBar  
                Dim o As Object = fi.GetValue(_ReportViewer)  
                Me.SetVisibleChangedCounter(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE)  
                Me.SetVisibleChangedCounter(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND)  
            End If 
        End Sub 
     
        Public Sub TearDownReportViewer()  
            Dim fi As Reflection.FieldInfo = _ReportViewer.GetType().GetField(TOOLSTRIP_CONTROL_NAME, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                'reportToolBar  
                Dim o As Object = fi.GetValue(_ReportViewer)  
                Me.NullRefOnUserPreferenceChanged(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE, Me._CurrentPageVisibleChangedCounter + Me._BufferCount)  
                Me.NullRefOnUserPreferenceChanged(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND, Me._TextToFindVisibleChangedCounter + Me._BufferCount)  
            End If 
        End Sub 
     
        Private Sub SetVisibleChangedCounter(ByVal o As ObjectByVal field As String)  
            Dim fi As Reflection.FieldInfo = o.GetType().GetField(field, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                Dim tb As ToolStripTextBox = CType(fi.GetValue(o), ToolStripTextBox)  
                'get the toolStripTextBoxControl  
                AddHandler tb.VisibleChanged, AddressOf Me.IncrementVisibleChangedCounter  
            End If 
        End Sub 
     
        Private Sub IncrementVisibleChangedCounter(ByVal sender As ObjectByVal e As EventArgs)  
            Dim tb As ToolStripTextBox = CType(sender, ToolStripTextBox)  
            Select Case tb.Name  
                Case TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE  
                    Me._CurrentPageVisibleChangedCounter += 1  
                Case TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND  
                    Me._TextToFindVisibleChangedCounter += 1  
            End Select 
        End Sub 
     
        Private Sub NullRefOnUserPreferenceChanged(ByVal o As ObjectByVal field As StringByVal numberOfHandlersToRemove As Integer)  
            Dim fi As Reflection.FieldInfo = o.GetType().GetField(field, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                Dim tb As ToolStripTextBox = CType(fi.GetValue(o), ToolStripTextBox)  
                'get the toolStripTextBoxControl  
                Dim tbc As Object = tb.Control  
                'create the delegate to OnUserPreferenceChanged  
                Dim d As [Delegate] = [Delegate].CreateDelegate(GetType(Microsoft.Win32.UserPreferenceChangedEventHandler), tbc, EVENTHANDLER_ON_USER_PREFERENCE_CHANGED)  
                'remove the handlers  
                For i As Integer = 0 To numberOfHandlersToRemove  
                    RemoveHandler Microsoft.Win32.SystemEvents.UserPreferenceChanged, CType(d, Microsoft.Win32.UserPreferenceChangedEventHandler)  
                Next 
            End If 
        End Sub 
     
        Private disposedValue As Boolean = False        ' To detect redundant calls  
     
        ' IDisposable  
        Protected Overridable Sub Dispose(ByVal disposing As Boolean)  
            If Not Me.disposedValue Then 
                If disposing Then 
                    Me._ReportViewer.Dispose()  
                    GC.Collect()  
                    GC.WaitForPendingFinalizers()  
                    GC.Collect()  
                End If 
            End If 
            Me.disposedValue = True 
        End Sub
    #Region " IDisposable Support "  
        ' This code added by Visual Basic to correctly implement the disposable pattern.  
        Public Sub Dispose() Implements IDisposable.Dispose  
            ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.  
            Dispose(True)  
            GC.SuppressFinalize(Me)  
        End Sub
    #End Region  
     
    End Class 

     

    • Proposed as answer by Randy Lee1 Thursday, June 5, 2008 12:10 AM
    Wednesday, June 4, 2008 11:59 PM
  • I forgot to check the bool for garbage collection.  Also, the SetUpReportViewer can be made private and added to the constructor.

    Public Class ReportViewerDisposer  
        Implements IDisposable  
     
        Private _ReportViewer As Microsoft.Reporting.WinForms.ReportViewer  
        Private Const TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE As String = "currentPage" 
        Private Const TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND As String = "textToFind" 
        Private Const TOOLSTRIP_CONTROL_NAME As String = "reportToolBar" 
        Private Const EVENTHANDLER_ON_USER_PREFERENCE_CHANGED As String = "OnUserPreferenceChanged" 
        Private _CollectGarbageOnDispose As Boolean = False 
        'start with a good buffer to insure removal of all delegates  
        Private _BufferCount As Integer = 20  
        Private _CurrentPageVisibleChangedCounter As Integer = 0  
        Private _TextToFindVisibleChangedCounter As Integer = 0  
     
        Public Property BufferCount() As Integer 
            Get 
                Return Me._BufferCount  
            End Get 
            Set(ByVal value As Integer)  
                Me._BufferCount = value  
            End Set 
        End Property 
     
        Public Property CollectGarbageOnDispose()  
            Get 
                Return Me._CollectGarbageOnDispose  
            End Get 
            Set(ByVal value)  
                Me._CollectGarbageOnDispose = value  
            End Set 
        End Property 
     
        Public Sub New(ByVal rptv As Microsoft.Reporting.WinForms.ReportViewer)  
            If rptv Is Nothing Then 
                Throw New ArgumentNullException("ReportViewer cannot be null.")  
            End If 
            Me._ReportViewer = rptv  
        End Sub 
     
        Public Sub SetUpReportViewer()  
            Dim fi As Reflection.FieldInfo = _ReportViewer.GetType().GetField(TOOLSTRIP_CONTROL_NAME, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                'reportToolBar  
                Dim o As Object = fi.GetValue(_ReportViewer)  
                Me.SetVisibleChangedCounter(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE)  
                Me.SetVisibleChangedCounter(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND)  
            End If 
        End Sub 
     
        Public Sub TearDownReportViewer()  
            Dim fi As Reflection.FieldInfo = _ReportViewer.GetType().GetField(TOOLSTRIP_CONTROL_NAME, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                'reportToolBar  
                Dim o As Object = fi.GetValue(_ReportViewer)  
                Me.NullRefOnUserPreferenceChanged(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE, Me._CurrentPageVisibleChangedCounter + Me._BufferCount)  
                Me.NullRefOnUserPreferenceChanged(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND, Me._TextToFindVisibleChangedCounter + Me._BufferCount)  
            End If 
        End Sub 
     
        Private Sub SetVisibleChangedCounter(ByVal o As ObjectByVal field As String)  
            Dim fi As Reflection.FieldInfo = o.GetType().GetField(field, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                Dim tb As ToolStripTextBox = CType(fi.GetValue(o), ToolStripTextBox)  
                'get the toolStripTextBoxControl  
                AddHandler tb.VisibleChanged, AddressOf Me.IncrementVisibleChangedCounter  
            End If 
        End Sub 
     
        Private Sub IncrementVisibleChangedCounter(ByVal sender As ObjectByVal e As EventArgs)  
            Dim tb As ToolStripTextBox = CType(sender, ToolStripTextBox)  
            Select Case tb.Name  
                Case TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE  
                    Me._CurrentPageVisibleChangedCounter += 1  
                Case TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND  
                    Me._TextToFindVisibleChangedCounter += 1  
            End Select 
        End Sub 
     
        Private Sub NullRefOnUserPreferenceChanged(ByVal o As ObjectByVal field As StringByVal numberOfHandlersToRemove As Integer)  
            Dim fi As Reflection.FieldInfo = o.GetType().GetField(field, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                Dim tb As ToolStripTextBox = CType(fi.GetValue(o), ToolStripTextBox)  
                'get the toolStripTextBoxControl  
                Dim tbc As Object = tb.Control  
                'create the delegate to OnUserPreferenceChanged  
                Dim d As [Delegate] = [Delegate].CreateDelegate(GetType(Microsoft.Win32.UserPreferenceChangedEventHandler), tbc, EVENTHANDLER_ON_USER_PREFERENCE_CHANGED)  
                'remove the handlers  
                For i As Integer = 0 To numberOfHandlersToRemove  
                    RemoveHandler Microsoft.Win32.SystemEvents.UserPreferenceChanged, CType(d, Microsoft.Win32.UserPreferenceChangedEventHandler)  
                Next 
            End If 
        End Sub 
     
        Private disposedValue As Boolean = False        ' To detect redundant calls  
     
        ' IDisposable  
        Protected Overridable Sub Dispose(ByVal disposing As Boolean)  
            If Not Me.disposedValue Then 
                If disposing Then 
                    Me._ReportViewer.Dispose()  
                    If Me._CollectGarbageOnDispose Then 
                        GC.Collect()  
                        GC.WaitForPendingFinalizers()  
                        GC.Collect()  
                    End If 
                End If 
            End If 
            Me.disposedValue = True 
        End Sub
    #Region " IDisposable Support "  
        ' This code added by Visual Basic to correctly implement the disposable pattern.  
        Public Sub Dispose() Implements IDisposable.Dispose  
            ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.  
            Dispose(True)  
            GC.SuppressFinalize(Me)  
        End Sub
    #End Region  
     
    End Class 
    Thursday, June 5, 2008 12:26 AM
  • I forgot to check the bool for garbage collection.  Also, the SetUpReportViewer can be made private and added to the constructor.

    Public Class ReportViewerDisposer  
        Implements IDisposable  
     
        Private _ReportViewer As Microsoft.Reporting.WinForms.ReportViewer  
        Private Const TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE As String = "currentPage" 
        Private Const TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND As String = "textToFind" 
        Private Const TOOLSTRIP_CONTROL_NAME As String = "reportToolBar" 
        Private Const EVENTHANDLER_ON_USER_PREFERENCE_CHANGED As String = "OnUserPreferenceChanged" 
        Private _CollectGarbageOnDispose As Boolean = False 
        'start with a good buffer to insure removal of all delegates  
        Private _BufferCount As Integer = 20  
        Private _CurrentPageVisibleChangedCounter As Integer = 0  
        Private _TextToFindVisibleChangedCounter As Integer = 0  
     
        Public Property BufferCount() As Integer 
            Get 
                Return Me._BufferCount  
            End Get 
            Set(ByVal value As Integer)  
                Me._BufferCount = value  
            End Set 
        End Property 
     
        Public Property CollectGarbageOnDispose()  
            Get 
                Return Me._CollectGarbageOnDispose  
            End Get 
            Set(ByVal value)  
                Me._CollectGarbageOnDispose = value  
            End Set 
        End Property 
     
        Public Sub New(ByVal rptv As Microsoft.Reporting.WinForms.ReportViewer)  
            If rptv Is Nothing Then 
                Throw New ArgumentNullException("ReportViewer cannot be null.")  
            End If 
            Me._ReportViewer = rptv  
        End Sub 
     
        Public Sub SetUpReportViewer()  
            Dim fi As Reflection.FieldInfo = _ReportViewer.GetType().GetField(TOOLSTRIP_CONTROL_NAME, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                'reportToolBar  
                Dim o As Object = fi.GetValue(_ReportViewer)  
                Me.SetVisibleChangedCounter(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE)  
                Me.SetVisibleChangedCounter(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND)  
            End If 
        End Sub 
     
        Public Sub TearDownReportViewer()  
            Dim fi As Reflection.FieldInfo = _ReportViewer.GetType().GetField(TOOLSTRIP_CONTROL_NAME, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                'reportToolBar  
                Dim o As Object = fi.GetValue(_ReportViewer)  
                Me.NullRefOnUserPreferenceChanged(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE, Me._CurrentPageVisibleChangedCounter + Me._BufferCount)  
                Me.NullRefOnUserPreferenceChanged(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND, Me._TextToFindVisibleChangedCounter + Me._BufferCount)  
            End If 
        End Sub 
     
        Private Sub SetVisibleChangedCounter(ByVal o As ObjectByVal field As String)  
            Dim fi As Reflection.FieldInfo = o.GetType().GetField(field, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                Dim tb As ToolStripTextBox = CType(fi.GetValue(o), ToolStripTextBox)  
                'get the toolStripTextBoxControl  
                AddHandler tb.VisibleChanged, AddressOf Me.IncrementVisibleChangedCounter  
            End If 
        End Sub 
     
        Private Sub IncrementVisibleChangedCounter(ByVal sender As ObjectByVal e As EventArgs)  
            Dim tb As ToolStripTextBox = CType(sender, ToolStripTextBox)  
            Select Case tb.Name  
                Case TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE  
                    Me._CurrentPageVisibleChangedCounter += 1  
                Case TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND  
                    Me._TextToFindVisibleChangedCounter += 1  
            End Select 
        End Sub 
     
        Private Sub NullRefOnUserPreferenceChanged(ByVal o As ObjectByVal field As StringByVal numberOfHandlersToRemove As Integer)  
            Dim fi As Reflection.FieldInfo = o.GetType().GetField(field, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                Dim tb As ToolStripTextBox = CType(fi.GetValue(o), ToolStripTextBox)  
                'get the toolStripTextBoxControl  
                Dim tbc As Object = tb.Control  
                'create the delegate to OnUserPreferenceChanged  
                Dim d As [Delegate] = [Delegate].CreateDelegate(GetType(Microsoft.Win32.UserPreferenceChangedEventHandler), tbc, EVENTHANDLER_ON_USER_PREFERENCE_CHANGED)  
                'remove the handlers  
                For i As Integer = 0 To numberOfHandlersToRemove  
                    RemoveHandler Microsoft.Win32.SystemEvents.UserPreferenceChanged, CType(d, Microsoft.Win32.UserPreferenceChangedEventHandler)  
                Next 
            End If 
        End Sub 
     
        Private disposedValue As Boolean = False        ' To detect redundant calls  
     
        ' IDisposable  
        Protected Overridable Sub Dispose(ByVal disposing As Boolean)  
            If Not Me.disposedValue Then 
                If disposing Then 
                    Me._ReportViewer.Dispose()  
                    If Me._CollectGarbageOnDispose Then 
                        GC.Collect()  
                        GC.WaitForPendingFinalizers()  
                        GC.Collect()  
                    End If 
                End If 
            End If 
            Me.disposedValue = True 
        End Sub
    #Region " IDisposable Support "  
        ' This code added by Visual Basic to correctly implement the disposable pattern.  
        Public Sub Dispose() Implements IDisposable.Dispose  
            ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.  
            Dispose(True)  
            GC.SuppressFinalize(Me)  
        End Sub
    #End Region  
     
    End Class 
    Thursday, June 5, 2008 12:26 AM
  •  A better solution...

    Clean up on FormClosed, not Deactivate (Duh!)

    The class is also improved so that now we remove the exact delegates causing the problem, instead of using the NUCLEAR approach!

    Public Class FormReport  
     
        Private _FixIt As Boolean = False 
        Private _ReportViewerDisposer As ReportViewerDisposer  
     
        Public Property FixIt() As Boolean 
            Get 
                Return Me._FixIt  
            End Get 
            Set(ByVal value As Boolean)  
                Me._FixIt = value  
            End Set 
        End Property 
     
        Private Sub FormReport_FormClosed(ByVal sender As ObjectByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed  
            If Me._FixIt Then 
                Me._ReportViewerDisposer.CollectGarbageOnDispose = True 
                Me._ReportViewerDisposer.Dispose()  
            End If 
        End Sub 
     
        Private Sub FormReport_Load(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles MyBase.Load  
            If Me._FixIt Then 
                Me._ReportViewerDisposer = New ReportViewerDisposer(Me.ReportViewer1)  
            End If 
            Me.ReportViewer1.RefreshReport()  
        End Sub 
     
    End Class 

    lll

    Public Class ReportViewerDisposer  
        Implements IDisposable  
     
        Private _ReportViewer As Microsoft.Reporting.WinForms.ReportViewer  
        Private Const TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE As String = "currentPage" 
        Private Const TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND As String = "textToFind" 
        Private Const TOOLSTRIP_CONTROL_NAME As String = "reportToolBar" 
        Private Const EVENTHANDLER_ON_USER_PREFERENCE_CHANGED As String = "OnUserPreferenceChanged" 
        Private Const LIST_HANDLERS As String = "_handlers" 
        Private Const ON_USER_PREFERENCE_CHANGED_EVENT As String = "OnUserPreferenceChangedEvent" 
        Private Const SYSTEM_EVENT_INVOKE_INFO As String = "SystemEventInvokeInfo" 
        Private Const TARGET_DELEGATE As String = "_delegate" 
        Private _CollectGarbageOnDispose As Boolean = False 
          
        Public Property CollectGarbageOnDispose()  
            Get 
                Return Me._CollectGarbageOnDispose  
            End Get 
            Set(ByVal value)  
                Me._CollectGarbageOnDispose = value  
            End Set 
        End Property 
     
        Public Sub New(ByVal rptv As Microsoft.Reporting.WinForms.ReportViewer)  
            If rptv Is Nothing Then 
                Throw New ArgumentNullException("ReportViewer cannot be null.")  
            End If 
            Me._ReportViewer = rptv  
        End Sub 
     
        Private Sub TearDownReportViewer()  
            Dim fi As Reflection.FieldInfo = _ReportViewer.GetType().GetField(TOOLSTRIP_CONTROL_NAME, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                'reportToolBar  
                Dim o As Object = fi.GetValue(_ReportViewer)  
                Me.NullRefOnUserPreferenceChanged(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE)  
                Me.NullRefOnUserPreferenceChanged(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND)  
            End If 
        End Sub 
     
        Private Sub NullRefOnUserPreferenceChanged(ByVal o As ObjectByVal field As String)  
            Dim fi As Reflection.FieldInfo = o.GetType().GetField(field, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                Dim tb As ToolStripTextBox = CType(fi.GetValue(o), ToolStripTextBox)  
                'get the toolStripTextBoxControl  
                Dim tbc As Object = tb.Control  
                'create the delegate to OnUserPreferenceChanged  
                Dim d As [Delegate] = [Delegate].CreateDelegate(GetType(Microsoft.Win32.UserPreferenceChangedEventHandler), tbc, EVENTHANDLER_ON_USER_PREFERENCE_CHANGED)  
                'get the SystemEvents _handlers dictionary  
                Dim handlers As Object = GetType(Microsoft.Win32.SystemEvents).GetField(LIST_HANDLERS, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Static).GetValue(Nothing)  
                'get the OnUserPreferenceChangedEvent object key  
                Dim upcHandler As Object = GetType(Microsoft.Win32.SystemEvents).GetField(ON_USER_PREFERENCE_CHANGED_EVENT, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Static).GetValue(Nothing)  
                'get a SystemEventInvokeInfo type  
                Dim systemEventInvokeInfo As Object = GetType(Microsoft.Win32.SystemEvents).GetNestedType(SYSTEM_EVENT_INVOKE_INFO, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
                'get the SystemEventInvokeInfo list for the UserPreferenceChangedEvent  
                Dim upcHandlerList As Object = handlers(upcHandler)  
                'initialize a target count  
                Dim targetCount As Integer = 0  
                Dim i As Integer = 0  
                'loop  
                While i < upcHandlerList.Count  
                    systemEventInvokeInfo = upcHandlerList(i)  
                    'get the SystemEventInvokeInfo._delegate field  
                    Dim target As [Delegate] = systemEventInvokeInfo.GetType().GetField(TARGET_DELEGATE, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance).GetValue(systemEventInvokeInfo)  
                    'eval  
                    If target.Target Is d.Target Then 
                        'increment on positive ID  
                        targetCount += 1  
                    End If 
                    i += 1  
                End While 
                'remove the handlers  
                For i = 1 To targetCount  
                    RemoveHandler Microsoft.Win32.SystemEvents.UserPreferenceChanged, CType(d, Microsoft.Win32.UserPreferenceChangedEventHandler)  
                Next 
            End If 
        End Sub 
     
        Private disposedValue As Boolean = False        ' To detect redundant calls  
     
        ' IDisposable  
        Protected Overridable Sub Dispose(ByVal disposing As Boolean)  
            If Not Me.disposedValue Then 
                If disposing Then 
                    Me.TearDownReportViewer()  
                    Me._ReportViewer.Dispose()  
                    If Me._CollectGarbageOnDispose Then 
                        GC.Collect()  
                        GC.WaitForPendingFinalizers()  
                        GC.Collect()  
                    End If 
                End If 
            End If 
            Me.disposedValue = True 
        End Sub
    #Region " IDisposable Support "  
        ' This code added by Visual Basic to correctly implement the disposable pattern.  
        Public Sub Dispose() Implements IDisposable.Dispose  
            ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.  
            Dispose(True)  
            GC.SuppressFinalize(Me)  
        End Sub
    #End Region  
     
    End Class 
    • Proposed as answer by Randy Lee1 Friday, June 6, 2008 1:05 AM
    Friday, June 6, 2008 1:03 AM
  • Here is the solution in C#.  It does require the use of VB compiler services for the late binding "requirements".  That should make all of you VB haters squirm a little.  If anyone knows how to accomplish this without the use of late binding, please post.  Microsoft really buried the SystemEvents internals, which I understand the reasoning, but this is the only way I can figure out to get to the delegates causing the leak.  Again, beware of future changes by Big M, and it probably wouldn't be a bad idea to do some error trapping on things like null FieldInfo objects, missing methods, etc...

    using System;  
    using System.Collections.Generic;  
    using System.ComponentModel;  
    using System.Data;  
    using System.Drawing;  
    using System.Linq;  
    using System.Reflection;  
    using System.Runtime.CompilerServices;  
    using System.Text;  
    using System.Windows.Forms;  
    using Microsoft.Reporting.WinForms;  
    using Microsoft.VisualBasic.CompilerServices;  
    using Microsoft.Win32;  
     
    public class ReportViewerDisposer : IDisposable  
        {  
            // Fields  
            private bool _CollectGarbageOnDispose = false;  
            private ReportViewer _ReportViewer;  
            private bool disposedValue = false;  
            private const string EVENTHANDLER_ON_USER_PREFERENCE_CHANGED = "OnUserPreferenceChanged";  
            private const string LIST_HANDLERS = "_handlers";  
            private const string ON_USER_PREFERENCE_CHANGED_EVENT = "OnUserPreferenceChangedEvent";  
            private const string SYSTEM_EVENT_INVOKE_INFO = "SystemEventInvokeInfo";  
            private const string TARGET_DELEGATE = "_delegate";  
            private const string TOOLSTRIP_CONTROL_NAME = "reportToolBar";  
            private const string TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE = "currentPage";  
            private const string TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND = "textToFind";  
     
            // Methods  
            public ReportViewerDisposer(ReportViewer rptv)  
            {  
                if (rptv == null)  
                {  
                    throw new ArgumentNullException("ReportViewer cannot be null.");  
                }  
                this._ReportViewer = rptv;  
            }  
     
            public void Dispose()  
            {  
                this.Dispose(true);  
                GC.SuppressFinalize(this);  
            }  
     
            protected virtual void Dispose(bool disposing)  
            {  
                if (!this.disposedValue && disposing)  
                {  
                    this.TearDownReportViewer();  
                    this._ReportViewer.Dispose();  
                    if (this._CollectGarbageOnDispose)  
                    {  
                        GC.Collect();  
                        GC.WaitForPendingFinalizers();  
                        GC.Collect();  
                    }  
                }  
                this.disposedValue = true;  
            }  
     
            private void NullRefOnUserPreferenceChanged(object o, string field)  
            {  
                FieldInfo fi = o.GetType().GetField(field, BindingFlags.NonPublic | BindingFlags.Instance);  
                if (fi != null)  
                {  
                    int i;  
                    ToolStripTextBox tb = (ToolStripTextBox)fi.GetValue(o);  
                    object tbc = tb.Control;  
                    Delegate d = Delegate.CreateDelegate(typeof(UserPreferenceChangedEventHandler), RuntimeHelpers.GetObjectValue(tbc), EVENTHANDLER_ON_USER_PREFERENCE_CHANGED);  
                    object handlers = typeof(SystemEvents).GetField(LIST_HANDLERS, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);  
                    object upcHandler = typeof(SystemEvents).GetField(ON_USER_PREFERENCE_CHANGED_EVENT, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);  
                    object systemEventInvokeInfo = typeof(SystemEvents).GetNestedType(SYSTEM_EVENT_INVOKE_INFO, BindingFlags.NonPublic | BindingFlags.Instance);  
                    object upcHandlerList = NewLateBinding.LateIndexGet(handlers, new object[] { upcHandler }, null);  
                    int targetCount = 0;  
                    for (i = 0; Operators.ConditionalCompareObjectLess(i, NewLateBinding.LateGet(upcHandlerList, null"Count"new object[0], nullnullnull), false); i++)  
                    {  
                        systemEventInvokeInfo = NewLateBinding.LateIndexGet(upcHandlerList, new object[] { i }, null);  
                        Delegate target = (Delegate)systemEventInvokeInfo.GetType().GetField(TARGET_DELEGATE, BindingFlags.NonPublic | BindingFlags.Instance).GetValue(systemEventInvokeInfo);  
                        if (target.Target == d.Target)  
                        {  
                            targetCount++;  
                        }  
                    }  
                    for (i = 1; i <= targetCount; i++)  
                    {  
                        SystemEvents.UserPreferenceChanged -= ((UserPreferenceChangedEventHandler)d);  
                    }  
                }  
            }  
     
            private void TearDownReportViewer()  
            {  
                FieldInfo fi = this._ReportViewer.GetType().GetField(TOOLSTRIP_CONTROL_NAME, BindingFlags.NonPublic | BindingFlags.Instance);  
                if (fi != null)  
                {  
                    object o = fi.GetValue(this._ReportViewer);  
                    this.NullRefOnUserPreferenceChanged(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_CURRENT_PAGE);  
                    this.NullRefOnUserPreferenceChanged(o, TOOLSTRIP_TEXTBOX_CONTROL_NAME_TEXT_TO_FIND);  
                }  
            }  
     
            // Properties  
            public bool CollectGarbageOnDispose  
            {  
                get 
                {  
                    return this._CollectGarbageOnDispose;  
                }  
                set 
                {  
                    this._CollectGarbageOnDispose = value;  
                }  
            }  
        } 
    • Proposed as answer by Randy Lee1 Friday, June 6, 2008 10:17 PM
    Friday, June 6, 2008 10:17 PM
  • A friend of mine informed me that even though you can't cast a generic type to any concrete type but a its actual type, you can cast it to an interface the type implements (thx Omar).  So here is the replacement function to avoid the late binding issue.

    VB
    Private Sub NullRefOnUserPreferenceChanged(ByVal o As ObjectByVal field As String)  
            Dim fi As Reflection.FieldInfo = o.GetType().GetField(field, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
            If fi IsNot Nothing Then 
                Dim tb As ToolStripTextBox = CType(fi.GetValue(o), ToolStripTextBox)  
                'get the toolStripTextBoxControl  
                Dim tbc As Object = tb.Control  
                'create the delegate to OnUserPreferenceChanged  
                Dim d As [Delegate] = [Delegate].CreateDelegate(GetType(Microsoft.Win32.UserPreferenceChangedEventHandler), tbc, EVENTHANDLER_ON_USER_PREFERENCE_CHANGED)  
                'get the SystemEvents _handlers dictionary  
                Dim handlers As Object = GetType(Microsoft.Win32.SystemEvents).GetField(LIST_HANDLERS, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Static).GetValue(Nothing)  
                'get the OnUserPreferenceChangedEvent object key  
                Dim upcHandler As Object = GetType(Microsoft.Win32.SystemEvents).GetField(ON_USER_PREFERENCE_CHANGED_EVENT, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Static).GetValue(Nothing)  
                'get a SystemEventInvokeInfo type  
                Dim systemEventInvokeInfo As Object = GetType(Microsoft.Win32.SystemEvents).GetNestedType(SYSTEM_EVENT_INVOKE_INFO, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)  
                'get the SystemEventInvokeInfo list for the UserPreferenceChangedEvent  
                Dim upcHandlerList As IList = CType(CType(handlers, IDictionary).Item(upcHandler), IList)  
                'initialize a target count  
                Dim targetCount As Integer = 0  
                Dim i As Integer = 0  
                'loop  
                While i < upcHandlerList.Count  
                    systemEventInvokeInfo = upcHandlerList(i)  
                    'get the SystemEventInvokeInfo._delegate field  
                    Dim target As [Delegate] = CType(systemEventInvokeInfo.GetType().GetField(TARGET_DELEGATE, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance).GetValue(systemEventInvokeInfo), [Delegate])  
                    'eval  
                    If target.Target Is d.Target Then 
                        'increment on positive ID  
                        targetCount += 1  
                    End If 
                    i += 1  
                End While 
                'remove the handlers  
                For i = 1 To targetCount  
                    RemoveHandler Microsoft.Win32.SystemEvents.UserPreferenceChanged, CType(d, Microsoft.Win32.UserPreferenceChangedEventHandler)  
                Next 
            End If 
        End Sub 
    C#

    private void NullRefOnUserPreferenceChanged(object o, string field)  
            {  
                FieldInfo fi = o.GetType().GetField(field, BindingFlags.NonPublic | BindingFlags.Instance);  
                if (fi != null)  
                {  
                    int i;  
                    ToolStripTextBox tb = (ToolStripTextBox)fi.GetValue(o);  
                    object tbc = tb.Control;  
                    Delegate d = Delegate.CreateDelegate(typeof(UserPreferenceChangedEventHandler), tbc, EVENTHANDLER_ON_USER_PREFERENCE_CHANGED);  
                    object handlers = typeof(SystemEvents).GetField(LIST_HANDLERS, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);  
                    object upcHandler = typeof(SystemEvents).GetField(ON_USER_PREFERENCE_CHANGED_EVENT, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);  
                    object systemEventInvokeInfo = typeof(SystemEvents).GetNestedType(SYSTEM_EVENT_INVOKE_INFO, BindingFlags.NonPublic | BindingFlags.Instance);  
                    IList upcHandlerList = (IList)((IDictionary)handlers)[upcHandler];  
                    int targetCount = 0;  
                    for (i = 0; i < upcHandlerList.Count; i++)  
                    {  
                        systemEventInvokeInfo = upcHandlerList[i];  
                        Delegate target = (Delegate)systemEventInvokeInfo.GetType().GetField(TARGET_DELEGATE, BindingFlags.NonPublic | BindingFlags.Instance).GetValue(systemEventInvokeInfo);  
                        if (target.Target == d.Target)  
                        {  
                            targetCount++;  
                        }  
                    }  
                    for (i = 1; i <= targetCount; i++)  
                    {  
                        SystemEvents.UserPreferenceChanged -= ((UserPreferenceChangedEventHandler)d);  
                    }  
                }  
            } 

    • Proposed as answer by Randy Lee1 Wednesday, June 11, 2008 11:05 PM
    Wednesday, June 11, 2008 11:05 PM
  • I appreciate Randy Lee's effort on solving this problem.
    However, for my usage (PDF exporting/rendering) I don't really use the object Microsoft.Reporting.WinForms.ReportViewer, but the  object Microsoft.Reporting.WinForms.ReportViewer.LocalReport, and only for PDF exporting/rendering.
    I've experiment the problems described by Brian Hartman in his Feb. 21, 2008 Post:

    #################  

    There are actually two problems here.  Running a report requires the report processing engine to generate and load an assembly in order to evaluate expressions in the report.  By default in local mode, this expression host assembly is loaded into the main appdomain.  Since assemblies can't be individually unloaded, it remains loaded permanently.

    Local mode does have the option to run expressions in a sandboxed appdomain (ReportViewer.LocalReport.ExecuteReportInSandboxAppDomain()).  However, there is currently a bug that this appdomain does not get unloaded at the appropriate time.  We are currently working on a fix for this and hope to get this into VS 2008 SP1.  If you need a fix sooner, customer support may be able to provide additional options: http://support.microsoft.com/contactussupport

    #################  

    and, for me, this is the main problem (for my type of usage).   
    I've used a workaround for the moment that, for now, is quite useful (workaround is: unload/load the AppDomain containing the ReportViewer.LocalReport object after a certain number of calls), waiting for VS 2008 SP1.


    andrea lazzarini
    Tuesday, July 1, 2008 4:12 PM
  • Here is a slightly simpler fix for the Winforms world. 

    public class ToolStripTextBoxFixer
    {
        public static void FixToolbarTextBoxes(Control view)
        {
            foreach (Control ctrl in view.Controls)
            {
                if (ctrl.GetType().Name.IndexOf("ToolStripTextBoxControl") >= 0) new ToolStripTextBoxFixer(ctrl);
                else FixToolbarTextBoxes(ctrl);
            }
        }

        bool m_visible = false;
        ToolStripTextBoxFixer(
    Control ctrl) { ctrl.VisibleChanged += this.onVisibleChanged; }
        void onVisibleChanged(object sender, EventArgs args)
        {
            Control ctrl = sender as Control;
            if (m_visible == ctrl.Visible)
            Microsoft.Win32.
    SystemEvents.UserPreferenceChanged -= (Microsoft.Win32.UserPreferenceChangedEventHandler
            Delegate.CreateDelegate(typeof(Microsoft.Win32.UserPreferenceChangedEventHandler), 
                    sender, 
    "OnUserPreferenceChanged");
            m_visible = ctrl.Visible;
        }
    }

    Usage pattern is to call FixToolbarTextBoxes() with a reference to a view that may contain a tool strip. This is done after InitializeComponent() runs (normally in the View's constructor). Once called, all text boxes will have an event handler attached that undoes any redundant subscriptions to SystemEvents.UserPreferenceChanged.  The non-generalized aspects of this workaround include, a hardcoded type name and a hardcoded references to the eventhandler method. These would need to be altered if the bug persisted in future versions.

    • Proposed as answer by Sentinel Friday, September 5, 2008 6:50 PM
    Friday, September 5, 2008 6:50 PM
  • I'm using ReportViewer control in asp.net project and detect memory leak. I have installed visual studio 2008 sp1 and ReportViewer sp1, however the new ReportViewer (Version 9.0.0) doesn't solve the problem.
    Could anybody provide some suggestion for web ReportViewer?
    Thank you very much.
    Thursday, October 16, 2008 12:08 PM
  • Could you post the load/unload code that you used.  We are experiencing the same issues with the control.
    Mike Waldrop
    Wednesday, May 6, 2009 12:07 AM
  • Hi Andrea,

    Could you please post the load/unload code? Trying to figure this out.
    Thanks
    Friday, May 15, 2009 3:41 PM
  • I'm having the same issue here and was told by microsoft to use the following code:

    If Session.Count > 0 Then
    For i As Integer = 0 To Session.Count - 1
    If Session(i).GetType().ToString() =
    "Microsoft.Reporting.WebForms.ReportHierarchy" Then
    Session.RemoveAt(i)
    End If
    Next
    End If

    This didn't work at all, plus the session object doesn't even exist if you're doing some background batch processing like I am.  Also unloading the appdomain would log off all of my users that are on my site.  Is there any other way to get these assemblies out?  Or possibly reuse an assembly in some manner?
    Is it possible to load the reportviewer assembly into a separate domain by itself and unload and reload just that?

    Friday, July 17, 2009 2:25 AM
  • I'm experiencing the same issue in VS 2005. Has Microsoft produced a fix yet? If so, will it work on VS 2005?

    Thanks,
    Fulano
    Friday, July 17, 2009 5:23 AM
  • Hi Randy Lee1, I implemented your "Fix" with all your updates, but it does not work. I am using VB 2005, WinForms. Is there anything I need to Import or add/edit that would prevent your code from working?

    Thanks,
    Fulano 
    Tuesday, July 21, 2009 10:34 PM
  • Randy, I got your code to work to some degree...however, I'm not sure what the full intent of the code was, but this is the results I get:

    I have two Forms. Form1 calls Form2, which is where the ReportViewer is located.

    1). When my ReportViewer Form loads (Form2) into memory, obviously the memory increases. -- I expected that.
    2). Without your code running, the memory increases every time you open Form2.  -- This is the bug
    3). With your code running the memory is limited (or capped) in the amount it increases.  -- Good solution!
    4). With your code running if I close Form2, the memory does NOT decrease back to what it was before Form2 loaded. -- Which is what I expected it to do...but it doesn't.
    5). To clear the memory I have to close Form1. 

    I have to say, you did a very nice job at finding a work-around, but is this the full intent of what you envisioned, or am I not applying it correctly? Is capping the memory what you intended, or was your solution intended to unload the memory Form2 took-up when it was originally loaded?

    Thanks for your help,
    Fulano 
    Wednesday, July 22, 2009 12:20 AM
  • I am also trying to resolve a memory leak with ReportViewer 2005 in LocalReport mode (it is an ASP.NET application). 
    Instead of loading the entire result set to the report viewer and using the built in paging, I am using my own custom paging mechanism to push a single page of records into ReportViewer at a time.  While profiling the application in .NET Memory Profiler, I can see my custom report data objects add to the total memory for each page.

    For example, if I am using the data source class 'CustomerInfo', and my custom page size is set to 25, then the number of CustomerInfo objects in memory increases by 25 every time I load a new page of data (25, 50, 75, etc).

    Moving to VS2008 is not an option at this point in time, so if there is no fix for the memory leak I am looking at potentially having to scrap my entire report framework for something else. 

    Any ideas?

    Jordan

     

    Thursday, July 30, 2009 1:14 AM
  • Hi Jordan, given your scenario, I don't think my "simple fix" would work, but take a look at this thread below so you can see what I observed and maybe it will help you. Of all the fixes I've seen out there (and trust me I've been searching for a ReportViewer memory leak fix for a while now) my observation is the one that drops the memory the most and the quickest.

    I implemented Randy's code, but his code seems to simply "cap" the leak at a certain level and does not remove the memory once the ReportViewer closes, which in my case is a real big problem, because my users render a report and then keep on working with the Form that called the ReportViewer. In this case, the CG never comes back and cleans the memory up.

    Having said that, its my opinion that we shouldn't hold our breath for an official fix from Microsoft. (Again my opinion here), but ReportViewer is an extremely immature product that was tossed out there for us to troubleshoot. It's as bad as anything I've ever seen and a product that had little if any business logic built into it. I'm seriously considering moving on to Crystal Reports and abandoning ReportViewer all together, but I have so much time invested into my reports and learning how to use this masterpiece of cryptic-coding that I'm trying to resolve the problem and simply move on.

    If you find any solution that can work, would you please post it here. I know I would appreciate it and maybe it would help Microsoft finally resolve this matter...I wouldn't expect a fix until VS 2010 rolls out in full (if that!). VS 2008 is not an option for me either due to this matter. I won't invest the money in something that I know from the get-go has bugs, which have not been addressed for years!!!

    Best of luck with your reports...


    http://social.msdn.microsoft.com/Forums/en-US/vsreportcontrols/thread/0da81cef-cb62-463f-9f4b-bd92f60a545e


    Fulano
    Thursday, July 30, 2009 3:54 AM
  • As MooFoo pointed out, it seems there is still no fix as of VS 2008 SP1.

    Perhaps a new thread should be started for this issue?  I'm the original poster, but I can't seem to log into the ID I started this thread under to reject the answer.
    Thursday, August 6, 2009 6:48 PM
  • Hello

    I`m getting the same problem, but i use on Web Forms and the problem happens there too,

    the problem happen with more intesive on render to PDF

    Have someome found another solve ?


    -=NFT=-
    Tuesday, August 11, 2009 6:16 PM
  • Hi Guys

    I recently ran into the much reported memory leak problems after writing a Win Forms tabbified application. 

    Now that VS 2010 Beta is out I can see that a new version of the Report Viewer dll is available (v10.0.0.0). I've not yet had a chance to try out the new version (my platform is based on SQL 2005 RDL), but wondered if anyone here had given it a go?

    I will try and put together a test app to try the new version soon and I will post back with results.
    Friday, November 27, 2009 12:41 PM
  • I've tried the new dlls but unfortunately i'm getting render errors.   So it's possible they changed the rendering code of the reports.   Anyone else try the 2010 version?
    Monday, December 14, 2009 9:40 PM
  • I updated to the 2010 version finally and it seems the problem may actually be worse.
    Wednesday, October 6, 2010 8:32 PM
  • I'm also experiencing similar issues with VS 2010 and Report Viewer 10.0.0.0.

    I've tried a number of approaches including output to PDF, TIFF, and EMF. I've also called the Dispose() method on the LocalReport object immediately after capturing the byte[] output from the Render() method (writing it to a File using BinaryWriter).

    I've filed a support case with Microsoft, although based on all of the other posts and bug reports I'm not hopeful they'll have an answer.

    Friday, October 8, 2010 3:03 PM
  • I think i have the same problem in VS 2010, everytime i open my form the application adds 2-7mb to its used memory, when i close it the amount does not get removed.

    Any new solutions or ideas how to solve this problem or what to dispose/close to get arround the leak?`

     

    Tuesday, October 12, 2010 1:19 PM
  • The memory leak is caused when the reportviewer loads the DLL to render the report.  Supposedly it now automatically runs in the sandbox app domain and you "should" be able to clear the sandbox app domain by using LocalReport.ReleaseSandboxAppDomain().  It most definitely is NOT working.  I've read a couple of places where people created an app domain on the fly and loaded the dll into it for rendering.  I haven't tried this yet myself, but considering this leak was promised to be fixed for years, I guess I don't have a choice.  Maybe someone else out there can tell us what we may be doing wrong.
    Wednesday, October 13, 2010 7:09 PM
  • Hi, the work-around mentioned above works well for me as implemented in http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/f900cfc2-e1ea-4abb-bb17-f17c302abbae

    VS2008, 3.51, I am foolish enough to want to spawn a dozen reports my users want to compare.

    Still running into the issue with FormerlyActiveMdiChild as mentioned in http://social.msdn.microsoft.com/Forums/en-US/clr/thread/fd14fd8c-da95-48a1-be1b-7b751f26fbf9 but at least that one's not permanent.

    Since Winforms are no longer emphasized, is there an ETA on ReportViewer controls for WPF and/or silverlight?

    Friday, October 15, 2010 8:17 AM
  • I think I've found a solution for my situation at least.  It seems that each subreport was also loading an assembly.  When I removed all the subreports the main report assembly seems to actually get released. 

    I initially tried LocalReport.ReleaseSandboxAppDomain() which didn't seem to help.  But what did finally work was

    LocalReport.SubreportProcessing -= new SubreportProcessingEventHandler(myeventhandler);

    Which I do right after I render the report and it seems to release the subreport assemblies.  Not sure if you have subreports, but take a look at this.  I also clear the datasources and set the parameters to an empty list also just to make sure everything is empty.  So my final code that seems to work is this,

            bytes = ReportViewer1.LocalReport.Render("PDF", null, out mime, out encoding, out filename, out stream, out warnings);

            ReportViewer1.LocalReport.DataSources.Clear();

            ReportViewer1.LocalReport.SetParameters(new List<ReportParameter>());

            ReportViewer1.LocalReport.SubreportProcessing -= new SubreportProcessingEventHandler(LocalReport_SubreportProcessing);

            ReportViewer1.LocalReport.ReleaseSandboxAppDomain();

     

    Friday, October 15, 2010 4:33 PM
  • Hello Everyone

    Did Microsoft Solve this problem ?

    Im Thinking to move to VS 2010 but only if i got a real solution for this

    tanks


    -=NFT=-
    Tuesday, May 24, 2011 12:52 PM
  • Hello,

     

    I have VS 2010, and I am using remote processing.  I still am getting memory use expansion issues.


    ddd
    Friday, July 8, 2011 3:16 PM
  • My current project is experiencing this issue with ReportViewer.  The winforms application was built on Framework 2.0 using ReportViewer version 8. 

    I've read many posts about this problem and decided to do some testing with Framework 3.5 and ReportViewer 9 and 10.  The problem did not go away.

    Can someone at Microsoft please provide an update on the status of this issue?  If it has been fixed, in what version with what framework?  Is there coding practices that alleviate the problem in the older versions of the control?

    Please help!  My client is having major issues with their application.  The memory is pegging several times per day requiring the users to shutdown the application and restart.  Not to mention the application sometimes flat out crashes due to low memory.

    Best regards,

    Keith


    Tuesday, August 9, 2011 3:24 PM
  • for web forms I deviced a solution in below thread. I want to do the same thing on User Logoff too and Kill all reportviewer reports in session.

    http://forums.asp.net/t/1030680.aspx/2/10

     


    abkisbond...
    Friday, September 23, 2011 10:17 AM