Задайте вопросЗадайте вопрос
 

ВопросFlowDocument Issue with Freeing Resources

  • 9 июня 2009 г. 7:48Jasson M Медали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     С кодом
    OVERVIEW:
    Hello, I have recently came across an issue that has proven to be out of my league. I have narrowed it down to being something with a FlowDocument and/or TextRange. Basically, when I load an RTF file into a FlowDocument via TextRange.Load() it will not release all the resources (or whatever it is called) after all the references to these objects scope has run out. In my WPF application I load large RTF files as well as load multiple small ones repeatedly. Consequently this becomes a major issue and the memory usage for my application begins to become an overwhelming amount.

    QUESTION:
    My question is how can I fix this?

    TRIED METHODS:
    I have used used GC.Collect() as well as GC.WaitForPendingFinalizers() (recommended by someone). These work to a small degree but overall the issue still persists. I have also made sure no references are being made to anything, even the parent of the FlowDocument (FlowDocumentPageViewer).

    Oh and another thing I tried was both Debug and non-Debug. Overall it doesn't make any difference.

    TEST CODE:
    Here is some test code that I have to test the problem.


    Code Behind:
    using System;
    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.IO;
    using System.Windows.Forms;
    
    namespace Testing
    {
        /// <summary>
        /// Interaction logic for Window1.xaml
        /// </summary>
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
            }
            private void LoadRTF(object sender, RoutedEventArgs e)
            {
                System.Windows.Forms.OpenFileDialog openDlg = new OpenFileDialog();
                openDlg.Filter = "Rich Text Format (*.rtf)|*.rtf";
                openDlg.ShowDialog();
                string path = openDlg.FileName;
                using (FileStream stream = new FileStream(path, FileMode.Open))
                {
                    TextRange range = new TextRange(richTextBox1.Document.ContentStart, richTextBox1.Document.ContentEnd);
                    range.Load(stream, System.Windows.DataFormats.Rtf);
                }
                //make sure the GC collects everything
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
            private void OnClose(object sender, EventArgs e)
            {
                richTextBox1 = null;
            }
        }
    }
    
    


    XAML:
    <Window x:Class="Testing.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300" ShowInTaskbar="False" Closed="OnClose">
        <Grid>
            <RichTextBox Name="richTextBox1" Margin="0,40,0,0" VerticalScrollBarVisibility="Visible" />
            <Button Name="loadButton" VerticalAlignment="Top" Click="LoadRTF">Load RTF</Button>
        </Grid>
    </Window>
    

    This code is something I quickly threw together for testing so I'm sure it is not optimal and also some of it is completely unnescessary. I was just testing out different things.

    I had another window that would open up this window so that way I'd be sure that when I closed that window all the references inside it would be disposed of - at least that's what I would think...

    Any help is greatly apprecaited. I have been attempting to solve this for coming up a week now and have not had any luck.

    FINAL NOTES:
    I am using WPF with .NET Framework 3.5 SP1 and VS2008 Professional.
    I also asked this question on StackOverflow. You can find it here .
    • ИзмененоJasson M 9 июня 2009 г. 7:53Where is the "Preview" Button >.>;
    • ИзмененоJasson M 9 июня 2009 г. 7:52
    • ИзмененоJasson M 9 июня 2009 г. 7:50XAML Code was messed up
    •  

Все ответы

  • 11 июня 2009 г. 23:34Hrach MSFTMSFTМедали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     
    Hi Jasson
    Usually when resources don't get freed it means not all references to them were cleaned. You'd need to do some memory usage analysis to find the problem.
  • 12 июня 2009 г. 10:29Jasson M Медали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     
    I have tried this before but didn't come across anything. I just tried it again right now and still didn't really come across anything. I am unsure what I am really looking for.
    I think that this is a fairly common problem given that it seems that just using TextRange.Load() causes it. I am still working on this...

    Thanks,
    ~Jasson
  • 12 июня 2009 г. 11:32xalnix Медали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     
    What is the "UndoLimit" on the RichTextBox (default is -1 which allows the undo queue to use all available memory).  Each time you load a new file into the RichTextBox, information is being saved to "undo" the edit (load).  Depending on the limit, the amount of apparent memory leak could add up.
    Les Potter, Xalnix Corporation, Yet Another C# Blog
  • 12 июня 2009 г. 12:02xalnix Медали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     
    TextRange.Load() and Undo do not cooperate very well, you should probably just turn it off before the load.  Turn it back on after the load.
    Les Potter, Xalnix Corporation, Yet Another C# Blog
  • 12 июня 2009 г. 12:48xalnix Медали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     С кодом
                    using (FileStream stream = new FileStream(path, FileMode.Open))
                    {
                        flowdoc = new FlowDocument();
                        flow.Document = flowdoc;
                        TextRange range = new TextRange(flowdoc.ContentStart, flowdoc.ContentEnd);
                        range.Load(stream, System.Windows.DataFormats.Rtf);
                    }
    
    
    Sorry, I lost track that this was about FlowDocument.  So, try allocating a new flow document and replacing the old.

        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="20"/>
                <RowDefinition />
            </Grid.RowDefinitions>
            <Button Grid.Row="0" Click="LoadRTF">Clickme</Button>
            <FlowDocumentPageViewer Grid.Row="1" Name="flow"        >
                <FlowDocument Name="flowdoc"></FlowDocument>
            </FlowDocumentPageViewer>
        </Grid>
    
    


    Les Potter, Xalnix Corporation, Yet Another C# Blog
  • 12 июня 2009 г. 18:19Jasson M Медали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     
    Changing the RichTextBox Undo isn't doing anything to the memory at least it is not the problem.

    I also tried your second suggestion but used a RichTextBox instead of a FlowDocumentPageViewer since that is what I am wanting to use in my application. This as well does not seem to have any affect on the memory. Thank you for the suggestion.

    ~Jasson
  • 20 июня 2009 г. 8:46Jasson M Медали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     
    Hello, I hate to bump this question but I am still looking for a solution. Any help is greatly appreciated. :)
  • 20 июня 2009 г. 18:05Hrach MSFTMSFTМедали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     
    I'd suggest to investigate this issues under deubgger. Please have a look at DumpHeap related suggestion on this thread - http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/2245ee83-6f51-4095-9ed2-d82bbeda61a8
    Let me know how it goes.
    On a side note - closing the first window does not guarantee that all resources it referenced will be collected, since they still may be referenced from other places, depending on the logic of application.
  • 21 июня 2009 г. 6:41Jasson M Медали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     С кодом
    Alright. I made an even simpler project to test this DumpHeap suggestion. This simple project only has a button inside the window. This is the click event for the button.

            private void Click_Button(object sender, EventArgs e)
            {
                RichTextBox b = new RichTextBox();
                FlowDocument f = new FlowDocument();
            }
    
    Using WinDbg, I clicked on the button a few (4) times and then checked the heap for both RichTextBox and FlowDocument types. Here is my log.


    0:007> .loadby sos mscorwks
    0:007> !DumpHeap -type System.Windows.Controls.RichTextBox
     Address       MT     Size
    013817c4 55cdc0a0      280    
    013cfbcc 55cdc0a0      280    
    013dba0c 55cdc0a0      280    
    013e7864 55cdc0a0      280    
    total 4 objects
    Statistics:
          MT    Count    TotalSize Class Name
    55cdc0a0        4         1120 System.Windows.Controls.RichTextBox
    Total 4 objects
    0:007> !DumpHeap -type System.Windows.Documents.FlowDocument
     Address       MT     Size
    013b7450 55cd21d8       84    
    013c6ec4 55cd21d8       84    
    013cfd4c 55cd21d8       84    
    013d3204 55cd21d8       84    
    013dbb8c 55cd21d8       84    
    013df054 55cd21d8       84    
    013e79e4 55cd21d8       84    
    013eae7c 55cd21d8       84    
    total 8 objects
    Statistics:
          MT    Count    TotalSize Class Name
    55cd21d8        8          672 System.Windows.Documents.FlowDocument
    Total 8 objects


    I dont know if I am doing this correctly since I have never used WinDbg before, but if I am right it looks like both of those are not being released because they are being held by reference somewhere else. But where? I am not sure how how I should use this information to investigate further.

    So my new questions are what is holding onto these and how can I go about releasing them? From what I understand this is a problem everyone has to deal with (or avoid). Thanks in advance :)
  • 21 июня 2009 г. 19:40Hrach MSFTMSFTМедали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     
    Interesting, how could there be 8 FlowDocuments if only 4 new operators were called:)
    Is the Click_Button the only added code in application? Can you share whole repro app. solution to debug it?
  • 21 июня 2009 г. 19:58Jasson M Медали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     С кодом
    If RichTextBox is not provided a FlowDocument when it is created then it will make its own. That is why there are twice as many FlowDocuments - this was expected this.

    Here is all of the code in the project.

    XAML
    <Window x:Class="WpfApplication2.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
        <Grid>
            <Button Click="Click_Button">Click</Button>
        </Grid>
    </Window>
    
    

    Code Behind
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    
    namespace WpfApplication2
    {
        /// <summary>
        /// Interaction logic for Window1.xaml
        /// </summary>
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
            }
            private void Click_Button(object sender, EventArgs e)
            {
                RichTextBox b = new RichTextBox();
                FlowDocument f = new FlowDocument();
            }
        }
    }
    
    

    That is all I added. Like I said, this is a really simple program but still has the problem. Try clicking on the button several times and watch the memory usage for this application go up and up. Thanks Hrach and good luck on your debugging attempt :)
  • 30 июня 2009 г. 5:01Tysonkb Медали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     
    I'm getting the same problem... I read a 140mb .rtf file into a TextRange, and memory sits at 240mb :( anyone solved this one yet?
  • 1 июля 2009 г. 0:22Jasson M Медали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     
    I have made a little progress in solving this but am still stuck. Here is a post I had on another site about this problem.


    It was the first time I used Windbg and so I didn't know what to do with the address to find the references. Here is what I got.



         Address       MT     Size
        0131c9c0 55cd21d8       84    
        013479e0 55cd21d8       84    
        044dabe0 55cd21d8       84    
        total 3 objects
        Statistics:
              MT    Count    TotalSize Class Name
        55cd21d8        3          252 System.Windows.Documents.FlowDocument
        Total 3 objects
        0:011> !gcroot 0131c9c0
        Note: Roots found on stacks may be false positives. Run "!help gcroot" for
        more info.
        Scan Thread 0 OSTHread 47c
        Scan Thread 2 OSTHread be8
        Scan Thread 4 OSTHread 498
        DOMAIN(001657B0):HANDLE(WeakSh):911788:Root:0131ff98(System.EventHandler)->
        0131fcd4(System.Windows.Documents.AdornerLayer)->
        012fad68(MemoryTesting.Window2)->
        0131c9c0(System.Windows.Documents.FlowDocument)
        DOMAIN(001657B0):HANDLE(WeakSh):911cb0:Root:0131ca90(MS.Internal.PtsHost.PtsContext)->
        0131cb14(MS.Internal.PtsHost.PtsContext+HandleIndex[])->
        0133d668(MS.Internal.PtsHost.TextParagraph)->
        0131c9c0(System.Windows.Documents.FlowDocument)
        DOMAIN(001657B0):HANDLE(WeakSh):9124a8:Root:01320a2c(MS.Internal.PtsHost.FlowDocumentPage)->
        0133d5d0(System.Windows.Documents.TextPointer)->
        0131ca14(System.Windows.Documents.TextContainer)->
        0131c9c0(System.Windows.Documents.FlowDocument)


    This is after I closed the window. So it looks like it is being referenced by a few things. Now that I know this how do I go about freeing up these references so they can release the FlowDocument?
  • 1 июля 2009 г. 16:29Ben Westbrook - MSFT Медали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     
    All the references above are weak references, so they shouldn't factor into keeping the FlowDocuments in memory.  Has the GC actually run yet?  If memory pressure is low, it won't be in any rush.

    Two things in general that I can think of that might be at fault here, aside from an outright bug where you're accidently holding a reference directly or indirectly.

    1. RichTextBox sometimes BeginInvokes a Dispatcher delegate to do some delayed initialization.  Your test app will need to run the Dispatcher queue (Dispatcher.Run) to free this reference.  If you're running with a default Application and just adding a handler for a Button press, this is already happening automatically.

    2. The GC won't actually run until it detects sufficient memory pressure.  Because RichTextBox has a finalizer, you need to be careful in how you force the collection:

    GC.Collect();
    GC.WaitingForPendingFinalizers();
    GC.Collect();

    Ben
  • 2 июля 2009 г. 22:55Jasson M Медали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     
    Hey Ben, I am running a default application and just adding in a handler for the button press so that should be taken care of automagically I believe. As for #2, I have done this and still have the issue.
  • 2 сентября 2009 г. 2:07ICCO Медали пользователяМедали пользователяМедали пользователяМедали пользователяМедали пользователя
     
    I have the same problem.