Saving a FixedDocument to an XPS file causes memory leak
Asked Answered
S

2

11

I have created a .NET Windows Service which performs certain actions and generates reports. These reports are XPS documents which I save in a certain directory.

Being familiar with WPF, the way I have chosen to create the reports is to instantiate a System.Windows.Documents.FixedDocument, adding FixedPage objects with content as required.

My problem is that the Service memory usage goes up and up and up over time as it runs.

At first, I went through my code rigorously, ensuring all disposable objects were disposed, etc, and other obvious memory leak candidates, but still had the problem. I then used the CLR Profiler to look at the memory usage of the Service in detail.

I found that as the service generates these FixedDocument reports, and saves them as XPS files, all the various UI elements associated with FixedDocument objects (Dispatcher, FixedPage, UIElementCollection, Visual, etc) are staying in memory.

This doesn't seem to happen when I do the same thing in my WPF apps, and so my hunch is that it has something to do with the WPF UI Dispatcher model being used outside of a WPF app.

How can I "dispose" my FixedDocument objects when using them in a service like this (or outside a WPF app in general)?

======== EDIT =========

OK, I've found that my memory leak is not specifically to do with creating/populating a FixedDocument. If I do so, but don't actually ever save it to disk as a XPS, the memory leak doesn't happen. So, my problem must be to do with the save as XPS file.

Here's my code:

var paginator = myFixedDocument.DocumentPaginator;
var xpsDocument = new XpsDocument(filePath, FileAccess.Write);
var documentWriter = XpsDocument.CreateXpsDocumentWriter(xpsDocument);                         
documentWriter.Write(paginator);
xpsDocument.Close();

What I've tried:

  • Manual garbage collection
  • Calling UpdateLayout() on each page of myFixedDocument before getting it's paginator (as suggested in the answer below) - I've also tried passing myFixedDocument directly into Write() i.e. not the paginator
  • Putting those lines of code in their own thread and manually shutting down Dispatchers

Still no luck.

========== WORKAROUND ==========

By isolating the above code into its own AppDomain using the general method shown in the example at http://msdn.microsoft.com/en-us/library/system.appdomain.aspx, the memory leak no longer affects my service (I say "no longer affects" because it still happens, but when the AppDomain is unloaded, all leaked resources are unloaded with it).

I would still be keen to see a real solution.

(On a related note, for those interested, using a separate AppDomain caused a memory leak in the PDFSharp component I was using to turn certain XPS files into PDF files. Turns out PDFSharp uses a global font cache that in normal circumstances doesn't grow significantly. But the cache was growing and growing after using these AppDomains. I edited the PDFSharp source code to enable me to manually clear out the FontDescriptorStock and FontDataStock, solving the issue.)

========== SOLUTION ==========

See my answer below for final solution.

Sweatshop answered 5/1, 2012 at 12:24 Comment(2)
check almost exact duplicate: #5884279Midpoint
The unconfirmed answer to that question is to force GC garbage collection. While I don't deem this an acceptable solution, I tried it anyway and it didn't work for me. Thanks anyway.Sweatshop
S
18

I eventually found an answer, which is two parts.

Firstly, after saving my XPS document to disk and closing/disposing the XpsDocument, I run the following line of code:

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

This gets rid of all the Dispatcher objects hanging around in memory.

While the above sorts out most of the memory issues, I noticed there were still FixedPage objects along with other UI objects still in memory. Manually clearing out my FixedDcoument seems to get rid of them:

foreach (var fixedPage in FixedDocument.Pages.Select(pageContent => pageContent.Child)) {
   fixedPage.Children.Clear();
}
Sweatshop answered 11/1, 2012 at 22:57 Comment(1)
thanks man, I was about to go down the "AppDomain" route, but this seems to work nicely! I also verified that it indeed seems to be an issue of Microsoft where FixedDocument is associated with static property "SerializableObjectContext" and never released from it. That kind of explains the memory leak.Yokoyokohama
M
0

From this, it looks like you have to call .UpdateLayout() at least once to avoid memory leak

Midpoint answered 5/1, 2012 at 12:31 Comment(3)
That question has to do with opening XPS documents, not creating FixedDocument objects in memory. Regardless, I tried the various solutions it mentions regarding the call of UpdateLayout(), and alas, none worked for me :-(Sweatshop
Can you post part of the code where it produces the problem. I can help find where it comes from.Midpoint
I edited my answer with a bit more information and some sample code - if you think you see a solution that'd be awesome.Sweatshop

© 2022 - 2024 — McMap. All rights reserved.