1

我添加了一些 .dispose() 和 .close() 调用,它们似乎帮助了一小部分,但一旦所有操作完成,我仍然看到应用程序使用 600MB 的 RAM。我错过了什么?

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    public void CreateCSVFile(DataTable dt, string strFilePath)
    {
        #region Export Grid to CSV
        // Create the CSV file to which grid data will be exported.
        StreamWriter sw = new StreamWriter(strFilePath, false);
        // First we will write the headers.
        //DataTable dt = m_dsProducts.Tables[0];
        int iColCount = dt.Columns.Count;
        for (int i = 0; i < iColCount; i++)
        {
            sw.Write(dt.Columns[i]);
            if (i < iColCount - 1)
            {
                sw.Write(",");
            }
        }
        sw.Write(sw.NewLine);
        // Now write all the rows.
        int current_row = 0;
        foreach (DataRow dr in dt.Rows)
        {
            current_row++;
            if (current_row % 1000 == 0)
            {
                lbl_progress.Text = current_row.ToString() + " of " + dt.Rows.Count.ToString();
                this.Refresh();
                Application.DoEvents();
            }
            for (int i = 0; i < iColCount; i++)
            {
                if (!Convert.IsDBNull(dr[i]))
                {
                    if (dr[i] is string)
                    {
                        sw.Write("\"" + dr[i].ToString().Replace(Environment.NewLine,", ").Replace("\"", "") + "\"");
                    }
                    else
                    {
                        sw.Write(dr[i].ToString());
                    };
                }
                if (i < iColCount - 1)
                {
                    sw.Write("|");
                }
            }
            sw.Write(sw.NewLine);
        }
        dt.Dispose();
        sw.Close();
        #endregion
    }

    private void button1_Click(object sender, EventArgs e)
    {
        button1.Enabled = false;
        string strConn = ConfigurationManager.AppSettings["sql_connection"].ToString();
        SqlConnection conn = new SqlConnection(strConn);
        for (int i = 1; i <= int.Parse(ConfigurationManager.AppSettings["query_count"].ToString()); i++)
        {
            try
            {
                SqlDataAdapter da = new SqlDataAdapter(System.Configuration.ConfigurationManager.AppSettings["sql" + i.ToString()].ToString(), conn);
                DataSet ds = new DataSet();
                da.Fill(ds);
                da.Dispose();
                DataTable dt = ds.Tables[0];
                ds.Dispose();
                textBox1.Text += "\r\n[" + DateTime.Now.ToString("HH:mm:ss") + "] Starting sql" + i.ToString();
                this.Refresh();
                Application.DoEvents();
                CreateCSVFile(dt, System.Configuration.ConfigurationManager.AppSettings["output_path"].ToString() + i.ToString() + ".csv");
                textBox1.Text += "\r\n[" + DateTime.Now.ToString("HH:mm:ss") + "] Finished sql" + i.ToString();
                dt.Dispose();
                this.Refresh();
                Application.DoEvents();
            }
            catch (Exception ex)
            {
                textBox1.Text += "\n" + ex.Message;
                //break;
            }
        }
        textBox1.Text += "\r\n[" + DateTime.Now.ToString("HH:mm:ss") + "] All done";
        button1.Enabled = true;
    }

}
4

3 回答 3

3

采取以下措施将感知到的 600 MB 减少到测量的 0 字节:

  • 您的所有DisposeClose调用都属于finally异常安全的块;或者更好的是,了解using评论已经建议的声明。这种风格问题是许多致命内存泄漏的根源,但鉴于您的样本有多小,您的情况可能并非如此。
  • 要测量分配和保留的内存,请GC.GetTotalMemory(true)在测量块之前和之后进行。不要将其留在生产代码中,除非您了解并愿意支付性能成本。
  • 还处置数据库连接。(这在客户端内存消耗方面是微不足道的,但不这样做可能会产生各种副作用。连接本身就是一种可耗尽的资源,它们也会消耗数据库服务器上的内存,而陈旧的连接可能会阻塞一些数据库操作或混淆人类。 )
  • 在同一过程中运行整个测量周期两次,忽略第一组结果。.NET 框架可能会在试运行期间为其自己的静态结构分配一些内存。仅当内存泄漏不断增长或具有潜在的无限大小时,才值得您注意。
于 2012-04-30T09:35:47.053 回答
3

首先,我要说 Disposal 与垃圾收集无关 - 处理某些东西不会将其标记为 GC,也不会回收任何托管内存。它是一种机制,代码可以通过它确定性地释放资源,通常是外部的或非托管的。

MSDN中撤出:

当下列条件之一为真时,就会发生垃圾收集:

  • 系统物理内存不足。
  • 托管堆上分配的对象使用的内存超过了可接受的阈值。这意味着
    已超过托管堆上可接受的内存使用量的阈值。
    随着流程的运行,此阈值会不断调整。
  • GC.Collect 方法被调用。在几乎所有情况下,您都不必调用此方法,因为垃圾收集器会持续运行。此方法主要用于特殊情况和测试。

这些是 GC 运行的条件。

我相信您在当前使用的 RAM 和仅具有大型工作集的应用程序之间感到困惑(操作系统已为进程提供内存,不一定使用)。它可能在峰值时使用了 600Mb,并且因为没有其他东西需要更多空间,所以与您的进程关联的 RAM 将被单独留下。任务管理器不能很好地判断一个进程实际使用了多少 RAM,而不能很好地判断它已经被分配了多少 RAM。

您确实需要使用适当的内存分析器来查看在给定时间点有多少对象处于活动状态。

此外,任何实现IDisposable都可以在using语句中使用:

using (var connection = new SqlConnection(""))
{
    connection.Open();
} // Dispose is called here, even on exception or return within using.

using语句只是在后台编译成try/finally块。

于 2012-04-30T12:05:47.240 回答
1

它不做垃圾收集 - 因为它不需要?

看,600mb 在您的计算机上可能并不多,并且 GC 仅在需要空间(即其他应用程序要求它)或达到阈值时才会启动。

所以,除非这是一个问题,否则它可能只是一个错误的观察。

运行内存分析器以查看实际内存负载是什么(它将强制收集,然后查看发生了什么并允许您分析什么使用内存以及为什么使用内存)。

不要(!)手动运行 GC.Collect,除非在非常罕见的情况下(基本上:你因为这样做而被解雇,现在争论为什么我不应该解雇你)。我需要的内存不是这样的条件 - GC.Collect 有不好的副作用(混合世代统计)。

于 2012-04-30T09:47:27.640 回答