【问题标题】:C# Reading XPS causing memory leakC# 读取 XPS 导致内存泄漏
【发布时间】:2018-07-22 08:34:26
【问题描述】:

我有一个仅读取 XPS 文件的简单程序,我已阅读以下帖子,它确实解决了部分问题。
Opening XPS document in .Net causes a memory leak

class Program
{
    static int intCounter = 0;
    static object _intLock = new object();

    static int getInt()
    {
        lock (_intLock)
        {
            return intCounter++;
        }
    }

    static void Main(string[] args)
    {

        Console.ReadLine();

        for (int i = 0; i < 100; i++)
        {

            Thread t = new Thread(() =>
            {
                var ogXps = File.ReadAllBytes(@"C:\Users\Nathan\Desktop\Objective.xps");
                readXps(ogXps);
                Console.WriteLine(getInt().ToString());
            });
            t.SetApartmentState(ApartmentState.STA);
            t.Start();
            Thread.Sleep(50);
        }
        Console.ReadLine();
    }


    static void readXps(byte[] originalXPS)
    {
        try
        {

            MemoryStream inputStream = new MemoryStream(originalXPS);
            string memoryStreamUri = "memorystream://" + Path.GetFileName(Guid.NewGuid().ToString() + ".xps");
            Uri packageUri = new Uri(memoryStreamUri);
            Package oldPackage = Package.Open(inputStream);
            PackageStore.AddPackage(packageUri, oldPackage);
            XpsDocument xpsOld = new XpsDocument(oldPackage, CompressionOption.Normal, memoryStreamUri);
            FixedDocumentSequence seqOld = xpsOld.GetFixedDocumentSequence();


            //The following did solve some of the memory issue
            //-----------------------------------------------
            var docPager = seqOld.DocumentPaginator;
            docPager.ComputePageCount();
            for (int i = 0; i < docPager.PageCount; i++)
            {
                FixedPage fp = docPager.GetPage(i).Visual as FixedPage;
                fp.UpdateLayout();
            }
            seqOld = null;
            //-----------------------------------------------


            xpsOld.Close();
            oldPackage.Close();
            oldPackage = null;
            inputStream.Close();
            inputStream.Dispose();
            inputStream = null;
            PackageStore.RemovePackage(packageUri);
        }
        catch (Exception e)
        {

        }
    }
}

^ 程序会读取一个 XPS 文件一百次 ^在应用修复之前

^应用修复后

所以帖子中建议的修复确实消除了一些对象,但是我发现仍然有像 DispatcherContextLayoutManagerMediaContext 这样的对象仍然存在于内存中,它们的数量正好是 100,这是正常的吗行为还是内存泄漏?我该如何解决?谢谢。

25/7/2018 更新
添加行 Dispatcher.CurrentDispatcher.InvokeShutdown(); 确实摆脱了 DispatcherContextLayoutManagerMediaContext 对象,不知道这是否是修复的理想方法。

【问题讨论】:

    标签: c# memory-leaks


    【解决方案1】:

    看起来你剩下的那些类来自 XPSDocument,它实现了 IDisposable 但你没有调用它们。还有一些实现相同接口的类,如果它们实现了,根据经验,要么将它们包装在 using 语句中,这样就可以保证调用它们的 Dispose 方法或调用它们的 Dispose 方法你自己。

    您的 readXps 方法的改进版本如下所示:

    static void readXps(byte[] originalXPS)
    {
        try
        {
            using (MemoryStream inputStream = new MemoryStream(originalXPS))
            {
                string memoryStreamUri = "memorystream://" + Path.GetFileName(Guid.NewGuid().ToString() + ".xps");
                Uri packageUri = new Uri(memoryStreamUri);
                using(Package oldPackage = Package.Open(inputStream))
                {
                    PackageStore.AddPackage(packageUri, oldPackage);
                    using(XpsDocument xpsOld = new XpsDocument(oldPackage, CompressionOption.Normal, memoryStreamUri))
                    {
                        FixedDocumentSequence seqOld = xpsOld.GetFixedDocumentSequence();
    
                        //The following did solve some of the memory issue
                        //-----------------------------------------------
                        var docPager = seqOld.DocumentPaginator;
                        docPager.ComputePageCount();
                        for (int i = 0; i < docPager.PageCount; i++)
                        {
                            FixedPage fp = docPager.GetPage(i).Visual as FixedPage;
                            fp.UpdateLayout();
                        }
                        seqOld = null;
                        //-----------------------------------------------           
                    } // disposes XpsDocument
                } // dispose Package
                PackageStore.RemovePackage(packageUri);
             } // dispose MemoryStream
        }
        catch (Exception e)
        {
            // really do something here, at least:
            Debug.WriteLine(e);
        }
    }
    

    这至少应该清理大部分对象。我不确定您是否会在分析中看到效果,因为这取决于在分析期间是否实际收集了对象。分析调试版本可能会产生意想不到的结果。

    由于这些对象实例的其余部分似乎绑定到 System.Windows.Threading.Dispatcher,我建议您可以尝试保留对线程的引用(但此时您可能会考虑查看任务)ansd 一次线程完成后,调用 Dispatcher 上的静态 ExitAllFrames

    您的主要方法将如下所示:

    Console.ReadLine();
    
    Thread[] all = new Thread[100];
    for (int i = 0; i < all.Length; i++)
    {
        var t = new Thread(() =>
        {
            var ogXps = File.ReadAllBytes(@"C:\Users\Nathan\Desktop\Objective.xps");
            readXps(ogXps);
            Console.WriteLine(getInt().ToString());
        });
        t.SetApartmentState(ApartmentState.STA);
        t.Start();
        all[i] = t; // keep reference
        Thread.Sleep(50);
    }
    foreach(var t in all) t.Join(); // https://stackoverflow.com/questions/263116/c-waiting-for-all-threads-to-complete
    all = null; // meh
    Dispatcher.ExitAllFrames(); // https://stackoverflow.com/a/41953265/578411
    Console.ReadLine();
    

    【讨论】:

    • 感谢您的详细提示!我正在使用 Release 构建进行分析,但这些对象仍然存在:\
    • 那么剩下的就是保持活跃的线程。与其将这些线程限定为内部 for 循环,不如将它们保存在一个数组中,在循环结束时将它们全部加入当前线程,然后调用 Dispatcher.ExitAllFrames();但如果这可以在没有副作用的情况下完成,我之前没有尝试过。因此,请谨慎使用并仔细阅读以防止意外
    • 我明白你的意思,但是线程一旦到达块的末尾就不会被“杀死”吗?
    • @nathan1658 好吧,是的。但是由于这些类似乎与 WPF 基础结构相关联,我可以想象它们“保留”线程,直到 appdomain 卸载,因为启动新线程会很昂贵。但我承认,我一直在挖掘参考资料以确认我的预感,这更值得一试。
    • 我想我已经解决了这个问题,虽然我不知道这是否是最理想的解决方案。似乎调用 XpsDocument 会将 WPF 调度程序带到 STA 线程,我必须手动将其关闭,否则它将保留在内存中。无论如何感谢您的帮助!
    猜你喜欢
    • 2023-03-14
    • 2012-02-03
    • 2015-09-02
    • 1970-01-01
    • 2017-02-20
    • 2019-10-21
    • 2016-03-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多