Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Saving a FixedDocument to an XPS file causes memory leak

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.

like image 427
Ross Avatar asked Jan 05 '12 12:01

Ross


1 Answers

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();
}
like image 171
Ross Avatar answered Oct 05 '22 23:10

Ross