【问题标题】:Reading from PackagePart stream does not release memory从 PackagePart 流中读取不会释放内存
【发布时间】:2011-04-18 16:29:44
【问题描述】:

在我们的应用程序中,我们正在使用 System.IO.Packaging.Package 类读取 XPS 文件。当我们从 PackagePart 的流中读取时,我们可以从任务管理器中看到应用程序的内存消耗增加了。但是,当读取完成时,内存消耗不会回落到从流中读取之前的水平。

为了说明问题,我编写了一个简单的代码示例,您可以在独立的 wpf 应用程序中使用它。

 public partial class Window1 : Window
 {
        public Window1()
        {
            InitializeComponent();

            _package = Package.Open(@"c:\test\1000pages.xps", FileMode.Open, FileAccess.ReadWrite, FileShare.None);

        }

        private void ReadPackage()
        {
            foreach (PackagePart part in _package.GetParts())
            {
                using (Stream partStream = part.GetStream())
                {
                    byte[] arr = new byte[partStream.Length];
                    partStream.Read(arr, 0, (int)partStream.Length);
                    partStream.Close();
                }
            }
        }

        Package _package;
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            ReadPackage();      
        }
 }

ReadPackage() 方法会将所有 PackagePart 对象的流内容读入本地数组。在示例中,为了方便查看应用程序的内存消耗变化,我使用了一个 1000 页的 XPS 文档作为包源。在我的机器上,独立应用程序的内存消耗从 18MB 开始,然后在调用该方法后上升到 100MB。再次调用该方法可以再次增加内存消耗,但它可以回落到 100MB。但是,它不再回落到 18MB。

有没有人在使用 PackagePart 时遇到过这种情况?还是我用错了?我认为 PackagePart 的内部实现是缓存读取的数据。

谢谢!

【问题讨论】:

  • 我不知道为什么这个问题被否决了。

标签: c# memory stream package xps


【解决方案1】:

您没有指定如何衡量应用程序的“内存消耗”,但也许您正在使用任务管理器?为了更好地了解正在发生的事情,我建议您检查应用程序的一些性能计数器。 .NET 堆和通用进程内存性能计数器均可用。

如果您真的想了解您的应用程序如何使用内存的详细信息,您可以使用Microsoft CLR profiler

您看到的可能是 .NET 堆扩展以容纳一个非常大的文件的结果。大对象放置在大对象堆 (LOH) 上,即使 .NET 内存被垃圾回收,空闲内存也永远不会返回给操作系统。此外,在垃圾回收期间,LOH 上的对象永远不会移动,这可能会使 LOH 碎片化,耗尽可用地址空间,即使有大量可用内存。

有没有人在使用 PackagePart 时遇到过这种情况?还是我用错了?

如果你想控制包使用的资源,你并没有以最好的方式使用它。包裹是一次性的,通常你应该像这样使用它:

using (var package = Package.Open(@"c:\test\1000pages.xps", FileMode.Open, FileAccess.ReadWrite, FileShare.None)) {
  // ... process the package
}

using 语句的末尾,包消耗的资源要么已经释放,要么可以被垃圾回收。

如果您真的想保留表单中的_package 成员,您应该在某个时候调用Close()(或IDisposable.Dispose())来释放资源。不建议调用GC.Collect(),也不一定能回收包使用的资源。无论您多久尝试强制进行垃圾回收,任何可从 _package 访问的托管内存(例如包缓冲区)都不会被垃圾回收。

【讨论】:

  • 感谢您的回复!我正在使用任务管理器。是的,将尝试 CLR 探查器作为另一种选择。我担心的是浪费时间试图找到一个解决方案,该解决方案实际上是内部 PackagePart 实现代码中的一个错误,只有微软才能修复。我还尝试将 PackagePart 流替换为将 1MB 文件的内容读入数组的 FileStream。这样做的次数与上面的代码相同。它基本上是相同的过程,但只是从不同的流中读取。在这种情况下,正在收集内存。它甚至没有达到 50MB。
  • 嗯。我尝试在调用 ReadPackage() 后立即调用 GC.Collect() 但什么也没发生。但是,我在 ReadPackage() 之后调用了 _package.Close() 然后 GC.Collect() 并且内存使用量从 100MB 下降到大约 20MB。所以也许包持有流的引用?
  • 在您的回答中看到您的更新。再次感谢。我只尝试了 GC.Collect() 来检查内存是否真的没有得到任何人的帮助。看起来它是直到包裹关闭。是的,当我们不再需要它时,我们确实会在包上调用 close。问题是它是我们应用程序中的主要数据来源,因此它必须在应用程序的整个生命周期内保持开放。 PackagePart 流是我们自始至终都不需要的东西。这就是为什么我们希望在离开 ReadPackage() 方法的范围时释放内存。也许这个包有某种内部缓存......
  • 我使用 Reflector 来反编译代码,尽管我不能说我完全理解发生了什么,但 PackagePart 对象似乎做了很多缓存。
  • 感谢您试用 Martin。我还尝试使用 CLR 探查器对其进行检查,似乎大部分内存都由 PackageParts 的 Byte 数组保存。我将尝试通过论坛向 Microsoft 询问此问题,并在我得到答复时更新此线程。
猜你喜欢
  • 2020-01-06
  • 2021-11-21
  • 2014-11-17
  • 1970-01-01
  • 1970-01-01
  • 2017-01-08
  • 1970-01-01
  • 2012-03-29
  • 2016-10-01
相关资源
最近更新 更多