【问题标题】:Memory occupied by my Application keeps increasing with time我的应用程序占用的内存随着时间的推移不断增加
【发布时间】:2023-03-07 17:09:01
【问题描述】:

我使用 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() 都必须在宿主表单的formclosure 事件中调用。否则每次调用 Report 都会增加内存大小。

更新 2

在我的系统中找到了内存泄漏的实际原因。我正在使用我在 flowlayoutpanel 中添加的复杂用户控件。

我使用普通的 foreach 循环来处理每个控件。但不知何故,只处理了一半的对象。我将此 foreach 循环嵌套在一个 for 循环中,计数器作为控件的数量。

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

【问题讨论】:

  • @JoeDF 是导致泄漏的方法的静态'ness'?或代码的任何特定部分
  • 该函数看起来没问题(除了缺少任何异常处理或 using/finally 子句来强制释放资源)。您分配和返回的表(DataTable dt = new DataTable();)是否可能永远不会被释放?
  • 你最好在 finally 块中释放命令、数据读取器和数据表对象。
  • @stevewellens 这个数据表最终成为winform控件的数据源..它是在释放控件时释放的
  • C# 是否允许在没有异常处理的情况下创建连接?如果没有,我建议您将连接包含在 try-catch 中并在 finally 块中关闭它。

标签: c# mysql winforms memory


【解决方案1】:

使用 Timer 而不是循环(将所有代码放在循环中以实现计时器滴答功能)。 或者把你所有的代码放在定时器滴答函数中。

【讨论】:

    【解决方案2】:

    SqlConnectionSqlCommandSqlDataReader 都是 IDisposable。前两个是另外密封的。假设您的 MySqlXXX 类封装了这些类,您需要通过实现 basic dispose pattern 使它们成为可处置的,并通过将它们包装在 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 仅作为 @ 键入987654344@。你可以像这样自己做:

    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();
        }
    }
    

    【讨论】:

    • 非常感谢您提供基于代码的 apt 解决方案。我在这里关心的是,当像 datagridview 这样的父控件被释放时,作为数据源的数据表是否会自行释放
    • @Akshay Zadgaonkar - 您需要向我们展示您认为可能存在泄漏的代码。或者至少,一个minimal, complete and verifiable 的例子。
    • @Akshay Zadgaonkar - 话虽如此,也许这是相关的:stackoverflow.com/questions/16238206/…
    • 释放对象并不能解决内存泄漏问题。这是 .NET 的两个不同方面。
    【解决方案3】:

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

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

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-03-07
        • 1970-01-01
        • 2013-11-26
        • 2013-08-10
        • 1970-01-01
        • 2018-09-03
        • 2011-11-14
        • 1970-01-01
        相关资源
        最近更新 更多