我的应用程序占用的内存随着时间的推移而不断增加

本文关键字:时间 不断增加 应用程序 内存 我的 | 更新日期: 2023-09-27 18:21:42

我使用C#、Winforms和Mysql开发了一个销售点系统。部署后,我观察到内存大小随着时间的推移而不断增加。在评估了我的代码之后,我觉得我的数据层可能是罪魁祸首。我已经使用类似的方法进行了一般化的数据库调用

 public static DataTable ExecQuery(string query, List<SqlParam> sp_params, string db)
 {
        MySqlConnection sCon = new MySqlConnection();
        sCon.ConnectionString = "server=" + server + ";port=" + port + ";database=" + db + ";uid=" + user + ";pwd=" + password + ";charset=utf8;";

        MySqlCommand command = new MySqlCommand();
        command.CommandType = CommandType.Text;
        command.Connection = sCon;
        if (sp_params != null)
        {
            for (int i = 0; i < sp_params.Count; i++)
            {
                MySqlParameter sparam = new MySqlParameter();
                sparam.ParameterName = sp_params[i].Name;
                sparam.MySqlDbType = sp_params[i].Type;
                sparam.Value = sp_params[i].Value;
                command.Parameters.Add(sparam);
            }
        }
        command.CommandText = query;
        sCon.Open();
        MySqlDataReader sd = command.ExecuteReader();

        DataTable dt = new DataTable();
        for (int fc = 0; fc < sd.FieldCount; fc++)
        {
            if (dt.Columns.Contains(sd.GetName(fc)))
            {
                dt.Columns.Add(sd.GetName(fc) + "1", sd.GetFieldType(fc));
            }
            else
            {
                dt.Columns.Add(sd.GetName(fc), sd.GetFieldType(fc));
            }
        }
        while (sd.Read())
        {
            DataRow dr = dt.NewRow();
            for (int fc = 0; fc < sd.FieldCount; fc++)
            {
                dr[fc] = sd.GetValue(fc);
            }
            dt.Rows.Add(dr);
        }
        sCon.Close();
        return dt;
    }

对于每个数据库调用,我们都使用此方法。前端只需要指定参数和查询。我怀疑这种静态方法会导致记忆问题,这是对的吗?

更新:

刚刚在我的应用程序中发现另一个漏洞:

无论在何处使用reportviewer,都必须在宿主窗体的窗体关闭事件中调用LocalReport.ReleaseSandboxAppDomain()。否则,每次调用Report都会增加内存大小。

更新2

在我的系统中找到了内存泄漏的实际原因。我正在使用一个复杂的用户控件,我将其添加到流程布局面板中。

我使用了一个普通的foreach循环来处理每个控件。。但不知何故,只处理了一半的物体。我将这个foreach循环嵌套在一个for循环中,其中counter作为控件的数量。

        int count = flwControls.Controls.Count;
        for (int i = 0; i < count; i++)
        {
            foreach (Control c in flwControls.Controls)
            {
                c.Dispose();
            }
        }

我的应用程序占用的内存随着时间的推移而不断增加

SqlConnectionSqlCommandSqlDataReader都是IDisposable。前两个是额外密封的。假设MySqlXXX类封装了这些类,则需要通过实现基本的dispose模式使它们成为可丢弃的,并通过将它们封装在using语句中来处理它们。

DataTable也是一次性的,所以一定要在代码的更高层使用它之后将其处理掉。

下面是一个如何处理这些资源的例子。(注意,我不能测试,因为我没有MySqlXXX类的定义):

    public static DataTable ExecQuery(string query, List<SqlParam> sp_params, string db)
    {
        using (MySqlConnection sCon = new MySqlConnection())
        using (MySqlCommand command = new MySqlCommand())
        {
            sCon.ConnectionString = "server=" + server + ";port=" + port + ";database=" + db + ";uid=" + user + ";pwd=" + password + ";charset=utf8;";
            command.CommandType = CommandType.Text;
            command.Connection = sCon;
            if (sp_params != null)
            {
                for (int i = 0; i < sp_params.Count; i++)
                {
                    MySqlParameter sparam = new MySqlParameter();
                    sparam.ParameterName = sp_params[i].Name;
                    sparam.MySqlDbType = sp_params[i].Type;
                    sparam.Value = sp_params[i].Value;
                    command.Parameters.Add(sparam);
                }
            }
            command.CommandText = query;
            sCon.Open();
            using (MySqlDataReader sd = command.ExecuteReader())
            {
                DataTable dt = new DataTable();
                for (int fc = 0; fc < sd.FieldCount; fc++)
                {
                    if (dt.Columns.Contains(sd.GetName(fc)))
                    {
                        dt.Columns.Add(sd.GetName(fc) + "1", sd.GetFieldType(fc));
                    }
                    else
                    {
                        dt.Columns.Add(sd.GetName(fc), sd.GetFieldType(fc));
                    }
                }
                while (sd.Read())
                {
                    DataRow dr = dt.NewRow();
                    for (int fc = 0; fc < sd.FieldCount; fc++)
                    {
                        dr[fc] = sd.GetValue(fc);
                    }
                    dt.Rows.Add(dr);
                }
                return dt;
            }
        }
    }
}

更新

在回答下面的问题时,我测试并发现,处理DataGridView或更改DataSource不会自动处理以前的DataSource——也许是因为DataSource只被键入为object。你可以自己这样做:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        dataGridView1.Disposed += dataGridView_Disposed;
    }
    static void dataGridView_Disposed(object sender, EventArgs e)
    {
        var dataGridView = sender as DataGridView;
        if (dataGridView != null)
        {
            var oldTable = dataGridView.DataSource as IDisposable;
            if (oldTable != null)
                oldTable.Dispose();
        }
    }
    private void FillDataGridView(object sender, EventArgs e)
    {
        var oldTable = dataGridView1.DataSource as IDisposable;
        DataTable table = GenerateTable();
        dataGridView1.DataSource = table;
        if (oldTable != null)
            oldTable.Dispose();
    }
}

内存泄漏。关闭scon后,需要释放它。

代码在内存泄漏方面看起来不错。

应用程序的其他代码可能导致内存泄漏。例如,此代码返回的DataTable是否保留在内存中?

上面的代码建议使用pooling=true以获得更好的性能。

使用Timer而不是循环(将所有代码放在计时器刻度函数的循环中)。或者把你所有的代码都放在定时器勾选功能中。