【问题标题】:How to free the memory used by a new class to avoid memory leak?如何释放新类使用的内存以避免内存泄漏?
【发布时间】:2015-05-13 13:26:31
【问题描述】:

我正在使用以下代码。正如我在 .Net 内存分析器中看到的那样,我发现内存泄漏正在发生。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace TestMemLeak
 {
 public partial class MainWindow : Window
  {

    public int count = 0;
    BackgroundWorker worker = new BackgroundWorker();
   public  class test 
    {
        public int sno{get; set;}
        public string col1 { get; set; }
        public string col2 { get; set; }
        public string col3 { get; set; }
        public string col4 { get; set; }
        public string col5 { get; set; }
        public string col6 { get; set; }
        public string col7 { get; set; }

    }
  // test test1 = new test();
    public MainWindow()
    {
        InitializeComponent();
        worker.WorkerReportsProgress = true;           // For Background Worker
        worker.DoWork += worker_DoWork;
        worker.ProgressChanged += worker_ProgressChanged;
        worker.WorkerSupportsCancellation = true;
        worker.RunWorkerCompleted += worker_RunWorkerCompleted;
    }


    private void Load_Click(object sender, RoutedEventArgs e)
    {
        dg1.Items.Clear();
        worker.RunWorkerAsync();
    }
    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
       // int max = (int)e.Argument;

        for (int i = 1; ; i++)
        {
            (sender as BackgroundWorker).ReportProgress(i);
           System.Threading.Thread.Sleep(1);


            if (worker.CancellationPending)
            {
                e.Cancel = true;
                return;
            }
        }
    }
    void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        int i = e.ProgressPercentage;


        if (i > 100000)
        {
            count++;
            if (count == 100)
            {
                for (int k = 0; k < 100; k++)
                { dg1.Items.RemoveAt(0); }
                count = 0;
            }
        }

            dg1.Items.Add(new test() {sno=i,col1="test",col2="test",col3="test",col4="test",col5="test",col6="test",col7="test" });



    }

    void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Cancelled)
        {
            System.Windows.MessageBox.Show("Task Cancelled.....");
        }

            // Check to see if an error occurred in the background process.

        else if (e.Error != null)
        {
            System.Windows.MessageBox.Show("Error while performing background operation." + e.Error.Message);
        }
        else
        {
            // Everything completed normally.
            System.Windows.MessageBox.Show("Task Completed..." + e.Result);
        }
    }

    private void Stop_Click(object sender, RoutedEventArgs e)
    {
        worker.CancelAsync();

    }

    private void Clear_Click(object sender, RoutedEventArgs e)
    {
      //  worker.CancelAsync();
        dg1.Items.Clear();
       // GC.Collect();
    }
}
}

每次我创建一个新的类测试时,它都会占用内存并且不会被 GC 释放。我怎样才能避免这种情况?

【问题讨论】:

  • 你只是在DataGrid中添加和清除项目,这些操作会导致内存泄漏吗?
  • 您能否添加内存分析器的输出,显示其中一个泄漏项目的 GC 根路径?另外,你的测试程序是什么?什么时候抓快照?
  • 您是否遇到了 OutOfmemoryException,或者您只是在查看应用程序使用的内存? GC.Collec()t 不会清除所有未使用的引用。您可能需要调用 GC.Collect(2),因为 Items 可能被视为长期存在的对象:msdn.microsoft.com/en-us/library/ee787088(v=vs.110).aspx

标签: c# wpf memory memory-leaks backgroundworker


【解决方案1】:

我看到你注册了这些事件:

worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;

您需要在某个时候取消注册它们。为此,您可以在您的类中实现 IDisposable 接口并在 Dispose 函数中取消注册事件。像这样:

public partial class MainWindow : Window, IDisposable
{

...

    public void Dispose()
    {
        worker.DoWork -= worker_DoWork;
        worker.ProgressChanged -= worker_ProgressChanged;
        worker.RunWorkerCompleted -= worker_RunWorkerCompleted;
    }
}

您还可以将 BackgroundWorker 设置为 null,以防万一。

【讨论】:

  • 事件处理程序只在工作进程中创建表单的强引用,而不是相反。 worker 被作为它所附加的表单的实例字段,所以那里没有泄漏。
  • 我试图将类声明为 Idisposable,但没有成功。你能根据那个修改我的代码吗
  • 这里不需要强制 Dispose
  • 能给个链接吗? @mgarant
【解决方案2】:

当您退订活动时

如果事件属于不同的类或者发布者的寿命比订阅者长,通常需要取消订阅这些事件。在这里, backgroundWorker 是你的类变量,它的生命周期范围等于这个类实例的生命周期。因此,无需取消订阅这些事件处理程序。

何时实施 IDisposable

如果您有任何已实现 IDispose 接口的类变量,您应该实现IDisposable pattern。简单来说,如果您有任何具有 Dispose 方法的类变量,您应该实现 IDispose 接口并 Dispose 所有一次性对象。

在您的代码中

显然,您的视图中有一些一次性对象(具有 Dispose 方法的对象)。 (我认为至少 DataGrid 是一次性的)实现 IDisposable 接口并处理所有一次性对象。

确保在实现 IDisposable 模式后调用适当的 Dispose 方法!只是执行它没有任何效果。

【讨论】:

  • “一般来说,只有当您订阅的发布者的寿命比您的订阅者长时,您才应该取消订阅事件”我不同意这种说法。首先,我认为这是错误的方式,(发布者持有对订阅者的引用,使其保持活动状态)。其次,可以和应该是非常不同的概念,ietf.org/rfc/rfc2119.txt。虽然取消订阅代码是不必要的,但通常不可能从快速浏览源代码中意识到这一点。
  • @Aron:嗯,我改变了表达方式。谢谢你的评论。关于发布者和订阅者,这正是我想说的。发布者的寿命更长,并保持对订阅者的引用。因此,GC 无法收集订阅者,即使它已被释放。
猜你喜欢
  • 2014-08-26
  • 2010-12-04
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
  • 2015-09-01
  • 2010-12-22
相关资源
最近更新 更多