Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FlowDocument Memory Issue in C#

I am currently attempting to deal with an issue with releasing a FlowDocument resources. I am loading an rtf file and putting it into a FlowDocument with TextRange.Load. I noticed that after it does this it holds onto those resources and GC doesn't collect it. I have ran a memory profiler and have seen that this is true. I have also narrowed it down to were I load actually put the rtf into the FlowDocument. If I dont do that, then everything is ok. So I know this is the issue.

I am hoping for some guidance to what how I can solve this problem. Here is the code that loads the rtf and everything. I have commented all of the other code out and even put it in its own scope as well as tried GC.Collect(). Any help is greatly appreciated.

EDIT: Here is my code in full at the moment. I have taken out everything else except for the bare essentials to get it running. The problem is still there. As you can see the FlowDocument and TextRange are not referenced anywhere else.

    public LoadRTFWindow(string file)
    {
        InitializeComponent();

        using (FileStream reader = new FileStream(file, FileMode.Open))
        {
            FlowDocument doc = new FlowDocument();
            TextRange range = new TextRange(doc.ContentStart, doc.ContentEnd);
            range.Load(reader, System.Windows.DataFormats.Rtf);
        }
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    }

I found this post, which I was hoping would help me solve my problem but I had no luck with it. Any type of help is greatly appreciated. Thank you.

EDIT: I figure I should mention the major way I am checking this. I have Windows Task Manager open and am watching the memory usage my application's process is using. When I run the above code the application goes from 40,000K to 70,000K while doing the TextRange.Load() (this is a large 400 page RTF) and once that finishes it drops down to 61,000K and stays there. My expectation is that it would drop back down to 40,000K or at least very close to it.

As I mentioned earlier I used a memory profiler and saw that there were LOTS of Paragraph, Run..ect. objects still Alive afterwards.

like image 971
Jasson Avatar asked Jun 04 '09 20:06

Jasson


2 Answers

If I've confirmed that there's a memory leak, here's what I would do to debug the problem.

  1. Install Debugging Tools for Windows from http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx#a
  2. Fire up Windbg from the installation directory.
  3. Launch your application and do the operations that leak memory.
  4. Attach Windbg to your application (F6).
  5. Type .loadby sos mscorwks
  6. Type !dumpheap -type FlowDocument
  7. Check the result of the above command. If you see multiple FlowDocuments, for each value of the first column (which contains the address), do

Type !gcroot <value of first column>

That should show you who's holding on to the reference.

like image 150
Senthil Kumar Avatar answered Sep 30 '22 06:09

Senthil Kumar


We had a similar problem in which we were creating flow document in different thread, i noticed in memory profiler that objects were still there.

So far as i know, as described in this link

"When a FlowDocument is created, relatively expensive formatting context objects are also created for it in its StructuralCache. When you create multiple FlowDocs in a tight loop, a StructuralCache is created for each FlowDoc. Let's you called Gc.Collect at the end of the loop, hoping to recover some memory. StructuralCache has a finalizer releases this formatting context, but not immediately. The finalizer effectively schedules an operation to release contexts at DispatcherPriority.Background."

So until the dispatcher operations are completed, Flow document will be in memory. So the idea is to complete the dispatcher operations.

If you are in a thread in which Dispatcher is currently running then try code below, it will force all the operations in queue to be completed, as SystemIdle is the lowest priority:

Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.SystemIdle, 
    new DispatcherOperationCallback(delegate { return null; }), null); 

If you are in a thread in which Dispatcher is not running, as in my case only single flow document was created in thread, so i tried something like:

var dispatcher = Dispatcher.CurrentDispatcher;
dispatcher.BeginInvokeShutdown(DispatcherPriority.SystemIdle);
Dispatcher.Run();

this will queue a shut down at very end and then will run the dispatcher to clean the FlowDocument and then in end it will shut down the dispatcher.

like image 24
Nitin Midha Avatar answered Sep 30 '22 06:09

Nitin Midha