views:

1016

answers:

8

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.

A: 

Make sure that the Parent of the FlowDocument isn't hanging around, see here. "Instantiating a FlowDocument automatically spawns a parent FlowDocumentPageViewer that hosts the content." If that control is hanging around it could be the source of your problem.

SpaceghostAli
I do not reference the parent anywhere. So I dont think the FlowDocumentPageViewer is my problem. I filled out the code more in the original post.
Jasson
+1  A: 

FlowDocument uses System.Windows.Threading.Dispatcher to free all resources. It doesn't use finalizers because finalizers will block current thread until all resources will be free. So the user may see some UI freezings and so on. Dispatchers are running in background thread and have less impact on the UI.
So calling GC.WaitForPendingFinalizers(); has no influence on this. You just need to add some code to wait and allow Dispatchers to finish their work. Just try to add something like Thread.CurrentThread.Sleep(2000 /* 2 secs */);

EDIT: It seems to me that you found this problem during debugging of some application. I wrote the following very simple test case (console program):

 static void Main(string[] args)
 {
  Console.WriteLine("press enter to start");
  Console.ReadLine();

  var path = "c:\\1.rtf";

  for (var i = 0; i < 20; i++)
  {
   using (var stream = new FileStream(path, FileMode.Open))
   {
    var document = new FlowDocument();
    var range = new TextRange(document.ContentStart, document.ContentEnd);

    range.Load(stream, DataFormats.Rtf);
   }
  }

  Console.WriteLine("press enter to run GC.");
  Console.ReadLine();

  GC.Collect();
        GC.WaitForPendingFinalizers();

  Console.WriteLine("GC has finished .");
  Console.ReadLine();
 }

I've tried to reproduce the problem. I run it several times and it was working perfectly - there were no leaks (about 3,2Kb all of the time and 36 handles). I couldn't reproduce it until I run this program in debug mode in the VS (just f5 instead of ctrl+f5). I received 20,5Kb at the beginning, 31,7Kb after loading and before GC and 31Kb after GC. It looks similar to your results.
So, could you please try to reproduce this problem running in release mode not under the VS?

zihotki
Yeah, I noticed that FlowDocument inherits from System.Windows.Threading.DispatcherObject. I had tried something similar to this previously and had the same outcome as before - nothing. It was worth a try though :). Thread.Sleep() is a static method btw.
Jasson
Oh, sorry, it was very late and I was a bit sleepy. I'll try today to check this problem and to debug a bit. It becomes very interesting.
zihotki
Thank you and good luck. I am really stumped on this one but am still continuing to look for a solution.
Jasson
I ran my application without debug mode and I am still receiving this problem. Furthermore I have become aware that this is not only happening in this instance, rather it is happening throughout my entire application. This is merely a condensed version of it. I use TextRange.Load() a decent amount since I have to display everything in a RichTextBox and store the data in byte[]. This becomes a major issue because after using the application for a while it will be using up an abundant amount of memory.
Jasson
I am not sure why this problem persists. Thank you for your assistance thus far.
Jasson
Is your application are commercial one? If you can share some sources where this problem can be reproduced I'll be glad to help to find what is wrong. Sometimes the problem lies in different place than we expect it. [email protected] - feel free to contact me.
zihotki
A: 

GC.Collect() by itself won't collect everything, you need to run:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Also, I've found the runtime doesn't always release collected memory right away, you should check the actual heap size instead of relying on task manager.

Eric
I tried that and it did seem to help a little as well as looking at the actual heap, which was a little better than what task manager was showing, but overall the issue remains. I am fairly certain it is some type of memory leak; however, I am failing to see what it is exactly.The code I supplied above is to my knowledge completely contained and consequently I am stumped because I have never dealt with anything like this before.Any other suggestions? Thanks for the help so far.
Jasson
A: 

Consider releasing that file handle. Also consider using the "using" statement instead of calling IDisposable.Dispose (no pun intended).

GregC
I actually have using in the code, I am unsure why I didn't put it in there. I will update the code in a sec. When you say I should consider releasing the file handle do you mean the FileStream? I am thinking not. I don't think I am sure on what you're talking about. Thanks for the help.
Jasson
Your newly edited code looks much cleaner. Consider the fact that C# code is JIT-compiled, so once you execute something, your working set gets bigger.Now you have a memory leak if you execute the same code over and over again (it's already JITted), and your allocated memory grows.I don't suppose you do have a memory leak with the above code.
GregC
I am pretty sure it is a memory leak. I did some playing around with the code and found that a significant amount of memory goes to a RichTextBox if that control is actually displayed in the window with text. It appears that most of this memory is not recovered when I close the window. Taking away the RichTextBox display, I saw that there is still a lot of memory going into the FlowDocument that isn't being recovered. This is pretty much the same as the RichTextBox issue since it has a FlowDocument in it. But the scope of this FlowDocument is very contained which is why I am stumped.
Jasson
A: 

I have yet been able to solve this. If anyone has any other suggestions I am very interested in hearing them. I would put a bounty of this but I dont think I have enough reputation. Thanks.

Jasson
A: 

I tried to reproduce your problem, but it doesn't happen on my machine.

Task Manager shows working set size, which isn't an accurate representation of a program's memory usage. Try using perfmon instead.

  1. Start -> Run -> perfmon.msc
  2. Add .NET CLR Memory/#Bytes in All Heaps for your application

Now repeat the experiment and check if that counter keeps going up. If it doesn't, it means you aren't leaking any managed memory.

Senthil Kumar
Using perfmon I discovered that in my example program it appears as though the first time I run it the memory is not recovered; however, subsequent runs appear to recover the memory. In my actual application the memory is not recovered at all. I am running this code in a second window. Possibly that is the reason why? But why would it recover the memory only the first time? I have also asked this question on the WPF MSDN forums. http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/15e2b42e-1975-4d68-8ddb-59bbcd1f0633/
Jasson
Do you have a reference to the second window lingering around? Have you subscribed to any events exposed by any of the components in the second window from the first window?
Senthil Kumar
+2  A: 

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.

Senthil Kumar
+1  A: 

I had previously done up #7. 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)

(I put it in a code block for easier reading). 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.

Thanks for your help. I feel like I am finally gaining some ground.

Jasson