我使用 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 循环中,计数器作为控件的数量。
int count = flwControls.Controls.Count;
for (int i = 0; i < count; i++)
{
foreach (Control c in flwControls.Controls)
{
c.Dispose();
}
}
最佳答案
SqlConnection
, SqlCommand
和 SqlDataReader
都是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
仅被键入为 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();
}
}
关于c# - 我的应用程序占用的内存随着时间的推移不断增加,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25823708/