积极答复者
将Glyphs对象输出到xps时,发生内存错误

问题
-
将含有Glyphs对象的对象,输出到xps文件时,发生下面的错误,Not enough storage is available to process this command.(HRESULT : 0x80070008)。
输出少量页数时基本没有问题,输出1000页时,执行到,Control的Measure时,发生例外。
代码如下
private void Button_Click(object sender, RoutedEventArgs e) { List<StackPanel> lstPnl = new List<StackPanel>(); for (int i = 0; i < 1000; i++) { StackPanel outputPnl = new StackPanel(); outputPnl.Height = 1024; outputPnl.Width = 768; for (int j = 0; j < 100; j++) { # region Glyphs Glyphs ctl = new Glyphs(); ctl.BeginInit(); ctl.FontUri = new Uri(@"C:\Windows\Fonts\msgothic.ttc"); ctl.StyleSimulations = StyleSimulations.BoldSimulation; ctl.FontRenderingEmSize = 9; ctl.HorizontalAlignment = System.Windows.HorizontalAlignment.Left; ctl.VerticalAlignment = System.Windows.VerticalAlignment.Center; ctl.Width = 500; ctl.Height = 10; ctl.Margin = new Thickness(2); ctl.UnicodeString = "Hello Everyone!___" + j; ctl.Fill = new SolidColorBrush(Colors.Red); ctl.EndInit(); # endregion outputPnl.Children.Add(ctl); } lstPnl.Add(outputPnl); } try { using (XpsDocument xpsDocument = new XpsDocument(@"C:\temp\test.xps", FileAccess.ReadWrite, CompressionOption.Maximum)) { XpsDocumentWriter xpsdw = XpsDocument.CreateXpsDocumentWriter(xpsDocument); // PrintTicket PrintTicket printTicket = new PrintTicket(); printTicket.PageOrientation = PageOrientation.Portrait; DateTime nowtime = DateTime.Now; VisualsToXpsDocument visWriter = xpsdw.CreateVisualsCollator(printTicket, printTicket) as VisualsToXpsDocument; foreach (StackPanel cav in lstPnl) { Size size = new Size(1024, 768); cav.Measure(size); cav.Arrange(new Rect(new Point(), size)); cav.UpdateLayout(); visWriter.Write(cav); } visWriter.EndBatchWrite(); Debug.WriteLine(((TimeSpan)(DateTime.Now - nowtime)).TotalSeconds.ToString()); } } catch (Exception ex) { Console.WriteLine(ex.Message); } }
* 开发环境,内存足够大(3G),硬盘空间容量也没问题
如果把Glyphs换成其他控件,例如TextBlock,不会有问题。因为Glyphs的效率更好一些,所以没有用TextBlock。为什么会发生意思内存错误呢?请高手指点。谢谢!
答案
-
戏剧性的,我的电脑 Windows 7 64位 4G内存,编译为x86和x64两种模式,都没任何问题,资源使用量虽然有点大,但算正常,并没有造成内存错误。下图是我的测试时候的内存使用和I/O用量:
可以看出,你的代码在运行时创建了大量的对象,但没有及时的GC掉,造成内存用量不断增加。在打印之后,也没有被回收,内存量保持着。
解决方法:
实时注意清空你的StackPanel,这样可以让GC实时回收一些资源。还有最后你可以设置List为null.
using (XpsDocument xpsDocument = new XpsDocument(@"C:\temp\test.xps", FileAccess.ReadWrite, CompressionOption.Maximum)) { XpsDocumentWriter xpsdw = XpsDocument.CreateXpsDocumentWriter(xpsDocument); // PrintTicket PrintTicket printTicket = new PrintTicket(); printTicket.PageOrientation = PageOrientation.Portrait; DateTime nowtime = DateTime.Now; VisualsToXpsDocument visWriter = xpsdw.CreateVisualsCollator(printTicket, printTicket) as VisualsToXpsDocument; foreach (StackPanel cav in lstPnl) { Size size = new Size(1024, 768); cav.Measure(size); cav.Arrange(new Rect(new Point(), size)); cav.UpdateLayout(); visWriter.Write(cav); cav.Children.Clear(); } visWriter.EndBatchWrite(); Debug.WriteLine(((TimeSpan)(DateTime.Now - nowtime)).TotalSeconds.ToString()); } lstPnl.Clear(); lstPnl = null; GC.Collect();
之后的内存使用量:
Bob Bao [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
- 已建议为答案 Jie BaoModerator 2011年8月10日 16:11
- 已标记为答案 FlyFish2011 2011年8月11日 10:01
全部回复
-
这种方式是不错,效率可以,但是任何进程都有它的寻址范围的,特别是32位进程,他的默认寻址空间只有4G,而用户能用的只有2G,除去线程,本地资源等其他所需要的空间,托管堆能用到的就没有多少了。所以你在输出1000页的时候,一定要考虑到你的进程的内存使用,不要过多的使用资源而不起释放,这样必然造成内存错误。 请一点点生成页面,不要一次性输出这么多。1000这个哥数量级很大了。 你可以用ProcessExplorer 工具看下你的进程,可顶在内存方面的使用超出预计了。
Sincerely,
Bob Bao [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
-
您好,感谢回答。
〉〉1000这个数量级很大了
如果换成TextBlock就没有问题,用Glyphs就报错,这是为什么呢?
〉〉你可以用ProcessExplorer工具看下你的进程,可顶在内存方面的使用超出预计了
我抓了个图
测试进程,使用的内存也就是114M左右,上面图片中,进城列表的倒数第8个(WpfApplication2.vshost.exe)为测试用的程序 -
你好,
我用Windbg来看下,看看是不是字体资源造成的。因为这种模式下,能产生问题我只能想到字体了。下午给你进展回复。
Sincerely,
Bob Bao [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
-
戏剧性的,我的电脑 Windows 7 64位 4G内存,编译为x86和x64两种模式,都没任何问题,资源使用量虽然有点大,但算正常,并没有造成内存错误。下图是我的测试时候的内存使用和I/O用量:
可以看出,你的代码在运行时创建了大量的对象,但没有及时的GC掉,造成内存用量不断增加。在打印之后,也没有被回收,内存量保持着。
解决方法:
实时注意清空你的StackPanel,这样可以让GC实时回收一些资源。还有最后你可以设置List为null.
using (XpsDocument xpsDocument = new XpsDocument(@"C:\temp\test.xps", FileAccess.ReadWrite, CompressionOption.Maximum)) { XpsDocumentWriter xpsdw = XpsDocument.CreateXpsDocumentWriter(xpsDocument); // PrintTicket PrintTicket printTicket = new PrintTicket(); printTicket.PageOrientation = PageOrientation.Portrait; DateTime nowtime = DateTime.Now; VisualsToXpsDocument visWriter = xpsdw.CreateVisualsCollator(printTicket, printTicket) as VisualsToXpsDocument; foreach (StackPanel cav in lstPnl) { Size size = new Size(1024, 768); cav.Measure(size); cav.Arrange(new Rect(new Point(), size)); cav.UpdateLayout(); visWriter.Write(cav); cav.Children.Clear(); } visWriter.EndBatchWrite(); Debug.WriteLine(((TimeSpan)(DateTime.Now - nowtime)).TotalSeconds.ToString()); } lstPnl.Clear(); lstPnl = null; GC.Collect();
之后的内存使用量:
Bob Bao [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
- 已建议为答案 Jie BaoModerator 2011年8月10日 16:11
- 已标记为答案 FlyFish2011 2011年8月11日 10:01
-
上面代码就是在输出之前把所有的可见元素进行一次绘制,否则他们是不会被WPF布局系统绘制变成可见的,从而就不会被打印了。
Size size = new Size(1024, 768);
cav.Measure(size);这两行就够了,第三行没必要。
一页只花到了 50/1000 = 50ms 速度很快了。
我们不能每次都考虑如何提高输出1000 10000页的情况的,衡量标准是每页花了多少,50ms已经是一个很不错的速度了。 至于你觉得花时间或者UI假死,你可以用多线程,或者进度条之类的动画来标示下。
Sincerely,
Bob Bao [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
-
上面代码就是在输出之前把所有的可见元素进行一次绘制,否则他们是不会被WPF布局系统绘制变成可见的,从而就不会被打印了。
Size size = new Size(1024, 768);
cav.Measure(size);这两行就够了,第三行没必要。
一页只花到了 50/1000 = 50ms 速度很快了。
我们不能每次都考虑如何提高输出1000 10000页的情况的,衡量标准是每页花了多少,50ms已经是一个很不错的速度了。 至于你觉得花时间或者UI假死,你可以用多线程,或者进度条之类的动画来标示下。
Sincerely,
Bob Bao [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
〉〉上面代码就是在输出之前把所有的可见元素进行一次绘制,否则他们是不会被WPF布局系统绘制变成可见的,从而就不会被打印了。那为什么第一页能够输出,从第二页开始输出空白,第一页为什么不是输出空白?
-
你好。
估计是环境的问题,我换了个Windows7 64位的环境,可以正常结束,
但是Windows7 32位的环境,却抱错。。。。
〉〉页只花到了 50/1000 = 50ms 速度很快了。
我用ActiveReport,输出同样的内容,同样的页数(1000页),才花了16秒左右,
因为一直达不到同ActiveReport同样的输出速度,多以一直在调查,本以为换成Glyphs可以好一些,
看了一下内存的情况,感觉还不如用TextBlock,生成XPS文件的速度也快不到哪里去,
如何才能够提高XPS生成的速度呢?
64每个进程允许的访址空间很大 2的64次方。所以应该不会有内存问题。WPF render的速度,由于受到它自身布局系统和每个元素Mesaure Arrange方法执行效率,DX底层绘制效率的影响,一直以来性能上都很难达到非常好。因为在你的代码中,每一页中每一个Text都要被他所在布局限制去计算大小和位置,而不像其他Report组件,有的是整页一次性输出,所以它的效率就仅是每页几毫秒的事。
Bob Bao [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
-
〉〉上面代码就是在输出之前把所有的可见元素进行一次绘制,否则他们是不会被WPF布局系统绘制变成可见的,从而就不会被打印了。
那为什么第一页能够输出,从第二页开始输出空白,第一页为什么不是输出空白?
我查了很多记录,发现在这个论坛上有这个帖子中提到了这个现象:http://social.expression.microsoft.com/Forums/en-US/wpf/thread/bdb2e1fa-544c-4b35-bd93-a2bb34e7c613/ 看simonlebon的一个回复:I have a case opened with MS right now on this issue. I have an outer StackPanel on my XAML page which consists of different page sections (inner StackPanels), and some of the larger pages don't all fit on to one printed page. As aforementioned in other posts, WPF only prints out the first page of contents. (i've tried both PrintDialog.PrintVisual(mystackpanel) and VisualsToXpsDocument.Write/EndBatchWrite)
Here's the response I have so far from MS:
I have bad news. I asked the developers about your issue and the response I got was that the printing APIs are designed to allow printing of a single visual as a page or multiple visuals as multiple pages.
So if multiple-page printing or pagination is not already provided, you can build up your own solution on top of the basic printing API. But you need to control how to get each individual visual and make sure it’s of the right size for the paper, and uses the right margin for the printer.。。。
所以看到这,我想到了袁峰的一篇博客,关于自己设计 pagination 来实现:http://blogs.msdn.com/b/fyuan/archive/2007/03/10/convert-xaml-flow-document-to-xps-with-style-multiple-page-page-size-header-margin.aspx
Sincerely,
Bob Bao [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.