I would like to generate (and then print or save) big XPS documents (>400 pages) from my WPF application. We have some large amount of in-memory data that needs to be written to XPS.
How can this be done without getting an OutOfMemoryException
? Is there a way I can write the document in chunks? How is this usually done? Should I not be using XPS for large files in the first place?
The root cause of the OutOfMemoryException
seems to be the creation of开发者_StackOverflow中文版 the huge FlowDocument
. I am creating the full FlowDocument
and then sending it to the XPS document writer. Is this the wrong approach?
How do you do it? You didn't show any code.
I use an XpsDocumentWriter to write in chunks, like this:
FlowDocument flowDocument = . .. ..;
// write the XPS document
using (XpsDocument doc = new XpsDocument(fileName, FileAccess.ReadWrite))
{
XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(doc);
DocumentPaginator paginator = ((IDocumentPaginatorSource)flowDocument).DocumentPaginator;
// Change the PageSize and PagePadding for the document
// to match the CanvasSize for the printer device.
paginator.PageSize = new Size(816, 1056);
copy.PagePadding = new Thickness(72);
copy.ColumnWidth = double.PositiveInfinity;
writer.Write(paginator);
}
Does this not work for you?
Speaking from perfect ignorance of the specific system involved, might I suggest using the Wolf Fence in Alaska debugging technique to identify the source of the problem? I'm suggesting this because other responders are not reporting the same problem you are experiencing. When working with easy-to-reproduce bugs Wolf Fence is dead simple to do (it doesn't work so well with race conditions and the like).
- Pick a midpoint in your input data and try to generate your output document from only that data, no more.
- If it succeeds, pick a point about 75% into the input and try again, otherwise pick a point at about 25% of the way into the input and try again.
- Lather, rinse, repeat, each time narrowing the window to where the works/fails line is.
- You may find that you fairly quickly identify one specific page -- or perhaps one specific object on that page -- that is "funny." Note: you only have to do this log2(N) times, or in this case 9 times given the 400 pages you mention.
Now you probably have something you can attack directly. Good luck.
You cannot use a single FlowDocument
for generating large documents because you will run out of memory. However if it is possible to generate your output as a sequence of FlowDocument
or as an extremely tall ItemsControl
, it is possible.
I've found the easiest way to do this is to subclass DocumentPaginator
and pass an instance of my subclass to XpsDocumentWriter.Write
:
var document = new XpsDocument(...);
var writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
writer.Write(new WidgetPaginator { Widget = widgetBeingPrinted, PageSize = ... });
The WidgetPaginator
class itself is quite simple:
class WidgetPaginator : DocumentPaginator, IDocumentPaginatorSource
{
Size _pageSize;
public Widget Widget { get; set; }
public override Size PageSize { get { return _pageSize; } set { _pageSize = value; } }
public override bool IsPageCountValid { return true; }
public override IDocumentPaginatorSource Source { return this; }
public override DocumentPaginator DocumentPaginator { return this; }
public override int PageCount
{
get
{
return ...; // Compute page count
}
}
public override DocumentPage GetPaget(int pageNumber)
{
var visual = ...; // Compute page visual
Rect box = new Rect(0,0,_pageSize.With, _pageSize.Height);
return new DocumentPage(visual, _pageSize, box, box);
}
Of course you still have to write the code that actually creates the pages.
If you want to use a series of FlowDocuments to create your document
If you're using a sequence of FlowDocuments
to lay out your document one section at a time instead of all at once, your custom paginator can work in two passes:
- The first pass occurs as the paginator is constructed. It creates a
FlowDocument
for each section, then gets aDocumentPaginator
to retrieve the number of pages. Each section'sFlowDocument
is discarded after the pages are counted. - The second pass occurs during actual document output: If the number passed to
GetPage()
is in the most recentFlowDocument
created,GetPage()
simply calls that document's paginator to get the appropriate page. Otherwise it discards that FlowDocument and creates aFlowDocument
for the new section, gets its paginator, then callsGetPage()
on the paginator.
This strategy allows you to continue to use FlowDocuments
as you have been, as long as you can break the data into "sections" each with its own document. Your custom paginator then effectively treats all the individual FlowDocuments as one big document. This is similar to Word's "Master Document" feature.
If you can render your data as a sequence of vertically-stacked visuals
In this case, the same technique can be used. During the first pass, all visuals are generated in order and measured to see how many will fit on a page. A data structure is built to indicate which range of visuals (by index or whatever) are found on a given page. During this process each time a page fills up, the next visual is placed on a new page. Headers and footers would be handled in the obvious way.
During the actual document generation, the GetPage()
method is implemented to regenerate the visuals previously decided to be on a given page and combine them using a vertical DockPanel or other panel of your choice.
I've found this technique more flexible in the long run because you don't have to deal with the limitations of FlowDocument
.
I can confirm that XPS does not throw out-of-memory on long documents. Both in theory (because operations on XPS are page-based, it doesn't try to load whole document in memory), and in practice (I use XPS-based reporting, and seen run-away error messages add up to many thousands of pages).
Could it be that the problem is in a single particularly large page? A huge image, for example? Large page with high DPI resolution? If single object in document is too big to be allocated at once, it will lead to out-of-memory exception.
Have you used sos to find out what is using up all the memory?
It could be that managed or unmanaged objects are being created during the production of your document, and they're not being released until the document is finished (or not at all).
Tracking down managed memory leaks by Rico Mariani could be of help.
like you say: probably the in-memory FixedDocument is consuming too much memory.
Maybe an approach in which you write out the XPS pages each individually (and make sure the FixedDocument gets released each time), and then use a merger afterwards could prove fruitful.
Are you able to write each page separately?
Nick.
ps. Feel free to concact me directly (info@nixps.com); we do a lot of XPS stuff at NiXPS, and I'm very interested in helping you getting this issue resolved.
精彩评论