WPF 窗口不停止计时器/关闭并降低 RAM 使用率

Posted

技术标签:

【中文标题】WPF 窗口不停止计时器/关闭并降低 RAM 使用率【英文标题】:WPF window not stopping timer/closing and lowering RAM usage 【发布时间】:2021-11-07 21:04:45 【问题描述】:

大家好,我一直在尝试很多方法来解决我的 WPF 程序问题。 WPF 是一项伟大的技术,可用于制作非常好的应用程序,但它与 WinForm 不同的奇怪命令有时让我感到不安。

我已加载主窗口和打开 MoviePosters 窗口的事件:

namespace WpfApplication49

    public partial class firstWindow : Window
    
        public static bool exitLoading = false;
        public static bool loadedMovies  = false;
        public static MoviePosters MP;

....[more code within this area]

    private void myControl1_Click(object sender, myClickEventArgs e)
    
        if (e.my.Tag.ToString().Contains("movieIcon"))
        
            if (!loadedMovies)
            
                MP = new MoviePosters();
                MP.Show();
            
            else
            
                MP.Show();
            
        

....[more code within this area]

        this.Topmost = false;
    

....[more code within this area]


我目前正在通过这种方式从我的 MoviePosters.xaml 窗口加载 watchMovie.xaml

namespace WpfApplication49

    public partial class MoviePosters : Window
    
        static watchingMovie MW;

    ....[more code within this area]

        private void myControl1_Click(object sender, myClickEventArgs e)
        
            MW = new watchingMovie((string)e.my.Tag.ToString())
            MW.Show();
        

    ....[more code within this area]
    

上面的内容可以很好地加载窗口。但是,问题是当我关闭此窗口并返回到原始窗口时,它仍然在内存中,并且计时器仍然处于活动状态!

watchingMovie.xaml 的结束代码:

namespace WpfApplication49

    public partial class watchingMovie : Window
    
        public readonly DispatcherTimer timer = new DispatcherTimer();

        public watchingMovie(String movie)
        
            InitializeComponent();
            passedMovie = movie;
            timer.Interval = TimeSpan.FromMilliseconds(200);
            timer.Tick += new EventHandler(timer_Tick);
            this.Topmost = true;
        

    ....[more code within this area]

        public void Exit_Click(object sender, RoutedEventArgs e)
        
            ME.Stop();
            ME.Close();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            timer.Stop();
            this.Close();
        

        void timer_Tick(object sender, EventArgs e)
        
            TimeSpan _duration = ME.NaturalDuration.TimeSpan;
            TimeSpan remaining = _duration.Subtract(ME.Position);

            if (!isDragging)
            
                seekBar.Value = ME.Position.TotalSeconds;
                currentposition = seekBar.Value;
                lblMovieTime.Content = remaining.ToString(@"hh\:mm\:ss");
            

            var totalTime = string.Format("0:00:1:00", (_duration.Hours * 60) + _duration.Minutes, _duration.Seconds);

        
            var currentTime = string.Format("0:00:1:00", (remaining.Hours * 60) + remaining.Minutes, remaining.Seconds);

            TimeSpan diff = remaining - _duration;
        

 ....[more code within this area]

    

firstWindow.xaml 开始,RAM 的起始位置为 ~59.0 MB。当它到达 MoviePoster 窗口时,使用的 RAM 为 ~105.4 MB。当我加载 watchingMovie 窗口时,它会上升到 ~145.0 MB。当我退出 watchingMovie 窗口(使用 this.Close())时,它会将我带回 MoviePoster 窗口,然后 RAM 为 ~143.0 MB强>。每次我加载 watchingMovie 窗口时,RAM 使用量都会增加!

所以...

内存不应该像以前那样回到 ~105 MB 左右吗 在加载 watchingMovie 窗口之前,因为我关闭了那个窗口? 我不明白为什么计时器仍在计算 电影——你知道,因为它应该停止一次 窗口本身已关闭?

这是一个简短的动画 GIF,显示从我的程序开始到 watchingMovie 窗口的退出。

更新 #1

【问题讨论】:

我不会担心 RAM 的使用,这很正常。所以你不应该打电话给GC.Collect。计时器仍在运行是一个问题。 Exit_Click 真的被调用了吗? 如果您期望通过调用 GC.Collect 将删除一个类的当前实例 - 不,它不会。它只能收集不再引用的对象。例如,您的变量 static watchingMovie MW 将保留对您正在观看的电影窗口的最后一个实例的引用。计时器还通过事件处理程序 timer_Tick 保持对您的窗口的引用。你公开了计时器,所以不清楚你还在用它做什么。但是您已经在使用诊断工具,只需在打开和关闭窗口之前制作快照并检查保留的引用。 先停止定时器,移除它的handler,等待200ms,关闭MediaElement,关闭Window。从计时器中删除public 修饰符,并从任何其他位置删除对此计时器的任何引用(如果有)。如果你误用了计时器,你在 WinForms 和 WPF 中的效果是一样的。 你在哪里销毁你的计时器?此外,点击事件处理程序?!人们从那些继续前进。 ME是媒体元素吗? 【参考方案1】:

在这种情况下调用 GC 完全没有意义。 由于计时器继续为你工作,这意味着对对象的一些引用仍然存在,只要有这些引用,GC就不会收集这些对象。

在窗口关闭事件中,需要停止定时器并清除所有静态链接。 像这样的:

    public watchingMovie(String movie)
    
        InitializeComponent();
        Closed += OnWindowClosed;

        passedMovie = movie;
        timer.Interval = TimeSpan.FromMilliseconds(200);
        timer.Tick += timer_Tick;
        this.Topmost = true;
    

    void timer_Tick(object sender, EventArgs e)
     
        // Some Code
    

    private void OnWindowClosed(object sender, EventArgs e)
    
        timer.Stop();
        timer.Tick -= timer_Tick;
        ME.Stop();
        ME.Close();
        ME = null;
    
        private void myControl1_Click(object sender, myClickEventArgs e)
        
            MW = new watchingMovie((string)e.my.Tag.ToString());
            MW.Closed += (_, __) => MW = null;
            MW.Show();
        
    public firstWindow()
    
        InitializeComponent();
        Closed += (_, __) => MP?.Close();
    

    private void myControl1_Click(object sender, myClickEventArgs e)
    
        if (e.my.Tag.ToString().Contains("movieIcon"))
        
            if (!loadedMovies)
            
                MP = new MoviePosters();
                MP.Closed += (_, __) => MP = null;
                MP.Show();
            
            else
            
                MP.Show();
            
        
    

P.S. 我强烈建议您完全放弃存储动态创建的对象的静态成员(字段和属性)。 静态成员只能用于存储对在应用程序会话期间必须存在的对象的引用。 在会话结束之前,此类对象不应关闭、销毁。

【讨论】:

按照您的建议,计时器似乎在退出时停止,但我现在在计时器本身上遇到错误。请参阅更新的 OP。虽然取出 ME = null; 解决了这个问题,但想确保真的不需要? 我无法准确判断您的应用程序的工作,因为显示的代码还不够。如果在关闭计时器后和/或在窗口关闭后,不再需要这些实例并且您需要从它们中清除内存,则需要将 ME、MP、MW 归零。报错截图显示你继续联系我。而且你必须自己决定它对你的程序逻辑的正确性:不要破坏 ME,或者如果它已经(或尚未)不引用 ME,则不要引用它。

以上是关于WPF 窗口不停止计时器/关闭并降低 RAM 使用率的主要内容,如果未能解决你的问题,请参考以下文章

如何关闭 WPF 中隐藏的主窗口?

在 WPF 中使用 WebBrowser 关闭窗口后音频继续播放

有啥方法可以停止 WPF 的 UI 窗口实例

如何创建一个倒数计时器,当屏幕关闭时停止并“等待”,重新打开时恢复。安卓

wpf combobox 关闭窗口后,再次调用不显示已经选择的值?

从框架内关闭 wpf 窗口