导航页面时是不是所有 UWP 应用都会泄漏内存?

Posted

技术标签:

【中文标题】导航页面时是不是所有 UWP 应用都会泄漏内存?【英文标题】:Do all UWP apps leak memory when navigating pages?导航页面时是否所有 UWP 应用都会泄漏内存? 【发布时间】:2018-09-24 16:30:24 【问题描述】:

所以我一直在深入研究 UWP,并在最新版本的 Windows 10 上使用 VS2017 v15.6.4 用 C# 开发一个简单的应用程序。

在运行该应用时,我注意到它的内存使用量会随着时间的推移而持续增加。

经过大量配对返回的代码,我得出的结论是这是由页面导航调用引起的,例如:

Frame.Navigate(typeof SomePage);
Frame.GoBack();
Frame.GoForward();

创建和观察这个过程非常容易......

1)在VS2017中,新建一个Blank App(Universal Windows)项目,命名为PageTest。

2) 向项目添加一个新的空白页面,将其命名为“NewPage”。

3) 将以下代码添加到 MainPage.xaml.cs:

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace PageTest

    public sealed partial class MainPage : Page
    
        DispatcherTimer timer = new DispatcherTimer();

        public MainPage()
        
            InitializeComponent();
            timer.Interval = TimeSpan.FromSeconds(.01);
            timer.Tick += Timer_Tick;
        

        protected override void OnNavigatedTo(NavigationEventArgs e)
        
            timer.Start();
        

        private void Timer_Tick(object sender, object e)
        
            timer.Stop();
            Frame.Navigate(typeof(NewPage));
        
    

4) 将以下(几乎相同)代码添加到 NewPage.xaml.cs:

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace PageTest

    public sealed partial class NewPage : Page
    
        DispatcherTimer timer = new DispatcherTimer();

        public NewPage()
        
            InitializeComponent();
            timer.Interval = TimeSpan.FromSeconds(.01);
            timer.Tick += Timer_Tick;
        

        protected override void OnNavigatedTo(NavigationEventArgs e)
        
            timer.Start();
        

        private void Timer_Tick(object sender, object e)
        
            timer.Stop();
            Frame.Navigate(typeof(MainPage));
        
    

您可以看到这个简单的测试应用包含 2 个页面,当它运行时,应用会以每秒 100 次的速度(通过计时器)自动在两个页面之间导航,直到您关闭应用。

5) 构建并运行应用程序。同时运行任务管理器并记下应用的初始内存占用。

6) 去泡杯咖啡。当你回来时,你会看到内存使用量增加了。而且还会继续增长。

现在我知道这个例子不切实际,但这里纯粹是为了证明我怀疑是影响大多数(如果不是全部)UWP 应用的基本问题。

试试这个...

运行 Windows 10 设置应用(由 Microsoft 开发的 UWP 应用)。同样,请注意它是任务管理器中的初始内存占用。 (在我的工具包中,它从大约 12.1 MB 开始)。

然后反复单击系统设置图标...然后返回按钮...然后系统设置图标...然后返回按钮...您明白了。并且观察内存占用量也增加了。

这样做几分钟后,我的 MS 设置应用程序内存消耗高达 90 MB 以上。

这种内存消耗似乎与 UWP 页面复杂性有关,如果您开始向页面添加大量 XAML 控件,尤其是图像控件,它会迅速增加。我的功能丰富的 UWP 应用很快就会消耗 1-2GB 内存。

所以这个“问题”似乎会影响所有基于框架的 UWP 应用。我已经在 3 台不同的 PC 上尝试了其他 UWP 应用程序,但我发现它们都存在相同的问题。

使用我的功能丰富的应用程序,内存消耗变得如此糟糕,以至于我现在正在考虑完全取消页面导航并将所有内容放在 MainPage 上。这不是一个令人愉快的想法。

行不通的潜在解决方案...

我遇到过描述类似问题的其他文章,并且我尝试过一些建议的解决方案,但没有任何区别......

1) 将以下任一行添加到 .xaml 页面定义都无济于事...

NavigationCacheMode="Required" 

NavigationCacheMode="Enabled" 

2) 切换页面时手动强制垃圾回收没有帮助。所以做这样的事情没有区别......

protected override void OnNavigatedFrom(NavigationEventArgs e)

    GC.Collect();

有人知道是否有解决方案,还是 UWP 应用的基本问题?

【问题讨论】:

你真的可以让它抛出一个内存不足的异常吗?因为“占用”内存是 .NET 的一项功能,仅仅是因为垃圾收集器是惰性的,并且如果没有其他人需要它,也不需要“释放”任何东西。毕竟,只有当您将它用于其他用途时,您的 RAM 是“已使用”还是“免费”才重要。 公平点。我会查的。也许值得一提的是,我多年来一直在为 WinForms/WPF 程序开发托管代码,其中不断增加的内存占用通常表明存在问题。也许 UWP 不一定是这种情况。 使用 VS2017 中的内存分析器并查看是否看到有关增长的详细信息。 您的代码导致了泄漏:您的 DispatcherTimer 的事件处理程序保留了对页面实例的引用,因此阻止了它被 GC'ed。 感谢您的回复。 Stefan:如果我添加 OnNavigatedFrom 事件代码如下: timer.Tick -= Timer_Tick;计时器=空;应用程序继续泄漏内存。那么有没有办法防止这种情况发生呢? 【参考方案1】:

我在导航时遇到了类似的行为。但它始终处于调试模式。在导航时,我的应用程序在调试模式下甚至超过(导航前 40Mb)100Mb。但在最终发布的产品中永远不会超过 1Mb。

【讨论】:

【参考方案2】:

我们在目标 >=RS3 (Win 10 1709) 时使用最新的 UWP 6.2.9 Nuget 更新在内存处理和 GC 调用方面做出了巨大改进。完整的发行说明是here

【讨论】:

【参考方案3】:

是的。这是 UWP 中的一个错误。几个月前我开了一张微软支持票,他们上周说他们发现了这个错误并解决了它们。他们将在 Windows Insider Preview 版本中提供下一次更新的修复(所以我认为下周 - 在 21.12.2018 上仍然不包括在内)。适用于所有人的修复程序将随 Windows 10 的春季更新一起提供。

【讨论】:

【参考方案4】:

在提供的重现代码中,您继续向前导航,这将创建页面实例的无限导航回栈(检查 Frame.BackStack.Count)。由于这些实例存储在内存中,因此应用的内存使用量自然会无限增长。

如果您更改代码以导航回 MainPage,并因此将 backstack 深度保持为 2,则内存不会随着反复来回导航而显着增长。

编辑 但是,如果我们在较长时间内观察到这一点,内存就会显着增加(在我的测试中,每次导航 1.5KB)。这是平台代码中的一个已知泄漏,截至 Windows 10 更新 1803 尚未解决。

您的测试项目的更新版本在这里共享:

NewPage.cs 的代码如下:https://1drv.ms/u/s!AovTwKUMywTNoYVFL7LzamkzwfuRfg

public sealed partial class NewPage : Page

    DispatcherTimer timer = new DispatcherTimer();

    public NewPage()
    
        InitializeComponent();
        timer.Interval = TimeSpan.FromSeconds(.01);
        timer.Tick += Timer_Tick;
    

    protected override void OnNavigatedTo(NavigationEventArgs e)
    
        timer.Start();
        long managedMem = GC.GetTotalMemory(true);
        ulong totalMem = Windows.System.MemoryManager.AppMemoryUsage;
        System.Diagnostics.Debug.WriteLine(string.Format("Managed Memory: 0 / Total Memory: 1", managedMem, totalMem));
    

    private void Timer_Tick(object sender, object e)
    
        timer.Stop();
        Frame.GoBack();
    

如果在您的实际应用中,您在几个导航周期后观察到 MB 大小泄漏,那么这不是由于上述平台错误,而是由于需要在特定应用中使用 VS 进行调查的其他内容例如内存分析器。通常,由于应用程序代码中的循环引用或静态事件处理程序可能会导致泄漏。调试这些的第一步是查看页面的终结器是否在强制 GC 时按预期命中。如果没有,请使用 VS 内存分析工具来确定哪些对象正在泄漏以及谁在保留它。该数据将有助于查明该特定案例的应用程序代码中的根本原因。通常这些是由于循环引用或未取消订阅的静态事件处理程序。如果您可以通过分析实际应用分享信息,我们很乐意在这方面提供更多帮助。

【讨论】:

感谢收看。我已将您的代码修改添加到我的测试应用程序中。托管内存保持不变,但总内存会随着时间的推移继续增加,并且永远不会减少。运行修改后的测试应用程序一个小时,报告的总内存数字从大约 29000000 上升到 113000000。我完整的测试应用程序项目在这里:link。如果你可以尝试这个并让它继续运行,希望你会看到相同的行为。我同意测试应用程序不代表我的真实应用程序,但它确实存在相同的泄漏 我忘了说。您在答案中提供的更新的测试项目也泄漏了我系统上的总内存。我对您的项目所做的唯一更改是将“解决方案平台”从 ARM 更改为 x86。以 x64 为目标时也会泄漏。再次感谢您的关注 感谢您的澄清,随着时间的推移,我确实看到来自平台的内存逐渐增长。我搜索了我们的数据库,发现这是一个尚未修复的已知错误。我会将此反馈链接到错误以获取更多客户证据。我还将更新我的答案以反映这一点。 在我的测试中(在 Win10 1803 上发布版本)从平台泄漏的大小似乎是每个导航大约 1.5KB。因此,如果我们将重现转换为用户速度,并假设用户在平均应用程序使用情况中的导航频率不超过每 5 秒,这意味着他们需要工作整整一个小时,每 5 秒导航一次,直到他们看到 1MB 的增长在内存使用方面。我并不是说修复这个问题并不重要,但如果在您的实际应用中,您在几次导航后发现 MB 大小泄漏,那么可能还有其他泄漏导致需要额外调查。 用例:一个非交互式的信息呈现系统,预计将永久运行数天,可能数周。并且一直在执行常规的程序化页面导航。所以虽然我猜这对 MS 来说是低优先级,但对我(可能只有我)来说,这是一个大问题。至少我现在又开始重新设计了。再次感谢您的宝贵时间。

以上是关于导航页面时是不是所有 UWP 应用都会泄漏内存?的主要内容,如果未能解决你的问题,请参考以下文章

Xamarin Forms 在 UWP 上是不是存在内存泄漏?

导航时发生 Javascript 内存泄漏

在 Xamarin 中,使用 Navigation.RemovePage() 时如何避免内存泄漏

UWP Windows 10 应用程序内存在导航时增加

在 UWP 中单击汉堡菜单项时如何提高应用程序性能?

如何使用UWP NavigationView后退按钮而不重新加载页面