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 ofmyFixedDocument
before getting it's paginator (as suggested in the answer below) - I've also tried passingmyFixedDocument
directly intoWrite()
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.
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();
}
From this, it looks like you have to call .UpdateLayout() at least once to avoid memory leak
来源:https://stackoverflow.com/questions/8742454/saving-a-fixeddocument-to-an-xps-file-causes-memory-leak