从PackagePart流中读取数据不会释放内存。

7

在我们的应用程序中,我们使用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的内部实现正在缓存已读取的数据。
谢谢!

我不知道为什么这个问题被踩了。 - Martin Liversage
1个回答

0

您没有说明如何测量应用程序的“内存消耗”,但也许您正在使用任务管理器?为了更好地了解情况,建议您检查一些应用程序的性能计数器。.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访问的托管内存(例如包缓冲区)都不会被垃圾回收,无论您多么频繁地尝试强制进行垃圾回收。

看到你在答案中的更新了,再次感谢。我只尝试了GC.Collect()来检查内存是否真的没有被任何人占用。看起来是这样的,直到包被关闭。是的,当我们不再需要它时,我们会调用包的close方法。问题是它是我们应用程序中的主要数据源,因此必须在整个应用程序的生命周期内保持打开状态。但是,PackagePart流是我们在整个过程中都不需要的东西。这就是为什么我们希望在离开ReadPackage()方法的范围时释放内存。也许包有一些内部缓存... - bjutus
@bjutus- 我也遇到了同样的问题,即由package stream保持的内存没有被释放。 你有任何更新吗..?? - Rohit Vats
到目前为止,我还无法确定这是否是原因,我正在学习使用包的遗留代码。有关包内存泄漏可能性的任何更新吗?@bjutus - Maslow
@bjutus:正如您所确定的那样,“泄漏”的内存是受管理的内存(包含包数据的字节缓冲区)。在您的应用程序中,有对这些缓冲区的引用,直到这些引用超出范围,内存才能被收集。无论是您的应用程序还是BCL包库都可能会保持这些引用处于范围内。BCL包库只能通过使用静态变量来实现,我怀疑Microsoft不会犯这样的愚蠢错误,因此我一定会去寻找您的代码中可能存在对您不再使用的包的引用的地方。 - Martin Liversage
@RohitVats 你找到解决方案了吗? - juFo
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接