完全配置/释放类对象C#

时间:2015-02-23 21:15:17

标签: c# .net vb.net memory memory-leaks

我为这篇长篇文章提前道歉......

我正在寻找一些关于如何处理我遇到的问题的建议,这些问题是我为客户编写的一些软件。简而言之,客户有一个第三方文件管理系统,存储他们的装运单,发票等。创建新文件时,客户需要保存和打印的副本,以便与他们销售的商品一起发货。第三方软件制造商生成一个带有.NET DLL的SDK,允许C#和VB.NET程序查询并保存文档。所以我写了一个程序,使用这个DLL定期扫描系统,当它找到新文件时,我的程序会将它们保存到临时目录并打印出来。一切都运行良好,除了SDK不是很好,所以每当调用保存文档的方法时,一堆东西被加载到RAM中,第三方SDK没有摆脱(即它没有管理记忆力非常好。有时客户端将运行大批量,这种RAM的累积将减慢他们的系统,并导致Out of Memory异常几次。我写了一些示例代码来模拟问题。它确实需要一点点想象力,但它会让你对我必须克服的问题有所了解。第一个代码示例模拟第三方DLL中的类。

“DLL”代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace DisposeSample
{
    public class OtherGuysDllClass
    {
        /*
            This is intended to simulate the SDK class library where the memory 
            leak occurs.  It ships with the third-party software that it integrates with, and 
            I can't change it.  Pretend this is a .dll that I referenced in my project.
        */

        public OtherGuysDllClass()
        {
            /*
                I wrote this to simulate a process that would build up in memory over time.  The SDK doesn't 
                do this per se, but something similar that causes junk to accumulate in RAM over time.
            */

            StreamWriter sw = new StreamWriter(Environment.CurrentDirectory + "\\output.txt");
            sw.WriteLine(DateTime.Now.ToString());
            sw.Close();
        }

    }
}

您可以看到上面的类中的代码包含一个未正确处理的StreamWriter对象,因此会导致一些垃圾留在内存中。同样,DLL不会完全这样做,但会导致内存问题,例如上面的示例。

我还编写了一个带有计时器控件的WinForms应用程序,定期从上面的类创建一个新对象,模拟我为客户编写的程序。

我的节目:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace DisposeSample
{
    public partial class Form1 : Form
    {

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            timer1.Interval = 500;
            timer1.Enabled = true;
        }


        /*
            Timer1 Tick Event Handler
            (This Timer Control was dragged and dropped onto the form in the designer).
        */
        private void timer1_Tick(object sender, EventArgs e)
        {
            OtherGuysDllClass dllObj = new OtherGuysDllClass();
            dllObj = null;

            /*
                Is there any way to make the program wipe the ddlObj and everything it created from 
                memory?  I tried calling GC.Collect(), but it didn't help much.
            */
        }

    }
}

因此,假设您获得了一个包含类似于顶层类的DLL,并且您有一个全天候运行的程序,可以定期创建该类的实例。你如何解决在内存中逐渐积累物体的问题。任何建议/建议将不胜感激。

谢谢!

1 个答案:

答案 0 :(得分:0)

我认为新的AppDomain是金钱! ++ user2864740

就像你说的那样,这种做法有点像RubeGoldberg-ish,但它完成了工作。它执行得足够快,内存使用率保持稳定。

这是我修改过的解决方案。该项目包含对.dll的引用,我将替换包含内存泄漏的第三方.dll。这是代码:

模拟第三方DLL的代码 -

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace OtherGuysDll
{
    public class OtherGuysCode
    {

        /// <summary>
        /// Example of an IDisposable that doesn't run in a using statement or call the Dispose method in a finally 
        /// block.  Used to simulate a third-party .dll that doesn't properly deallocate resources and can cause 
        /// problematic accumulation in RAM over time.
        /// </summary>
        public OtherGuysCode()
        {
            /*
                The code below demonstrates a chunk of unmanged code that doesn't deallocate resources 
                properly.  Feel free to substitute any code that will cause a memory leak below if you'd prefer a 
                different example.
            */

            StreamWriter sw = new StreamWriter(Environment.CurrentDirectory + "\\output.txt");
            sw.WriteLine(DateTime.Now.ToString());
            sw.Close();
        }

    }
}

同样,这用作第三方SDK中的.dll的替代品。我无法控制此代码,无法更改它。我在我的项目中添加了一个单独的类,它实现了MarshalByRefObject类,并包含一个公共方法来创建问题的单个实例.dll:

创建新域实例类的代码 -

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DisposeSample
{
    /*
        A new instance of this class is created by the New Domain object 
        in each of the Form1's Scan Timer Tick Event.
    */
    public class CreateNewDomainInstanceClass : MarshalByRefObject
    {

        /// <summary>
        /// This method creates a new instance of the problem DLL.
        /// </summary>
        public void RunOtherGuysCode()
        {
            // Create a new instance of the Other Guy's Code class
            OtherGuysDll.OtherGuysCode ogc = new OtherGuysDll.OtherGuysCode();
        }

    }
}

最后,我修改了ScanTimer Tick事件,以便在每个tick中处理新域中的Create New Domain Instance Class:

包含ScanTimer的表单的代码 -

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;


namespace DisposeSample
{
    public partial class Form1 : Form
    {

        // Default Constructor
        public Form1()
        {
            InitializeComponent();
        }


        // Form Load Event Handler
        private void Form1_Load(object sender, EventArgs e)
        {
            timer1.Interval = 500;
            timer1.Enabled = true;
        }



        /*
            Timer1 Tick Event Handler
            (This Timer Control was dragged and dropped onto the form in the designer).
        */
        private void timer1_Tick(object sender, EventArgs e)
        {
            // Create a new App Domain object
            AppDomain newDomain = AppDomain.CreateDomain("Other Guy's DLL");

            // Get the Assembly Name of the Create New Domain Instance Class
            string aName = typeof(CreateNewDomainInstanceClass).Assembly.FullName;

            // Get the Full Name of the Create New Domain Instance Class
            string tName = typeof(CreateNewDomainInstanceClass).FullName;

            // Create a new instance of the Create New Domain Instance Class in the new App Domain
            CreateNewDomainInstanceClass ndInstance = (CreateNewDomainInstanceClass)newDomain.CreateInstanceAndUnwrap(aName, tName);

            // Run the public method in the new instance
            ndInstance.RunOtherGuysCode();

            // Unload the new App Domain object
            AppDomain.Unload(newDomain);

            // Call an immediate Garbage Collection to ensure that all unused resources are removed from RAM
            GC.Collect();
        }

    }
}

这种方法似乎可以很好地完成工作,而且就像我能做到的那样直截了当。我希望这可以帮助其他可能遇到类似问题的人。再次,特别感谢user2864740建议AppDomain角度。