【问题标题】:Dispose/Release Class Object Completely C#完全释放/释放类对象 C#
【发布时间】:2015-04-25 08:32:22
【问题描述】:

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

我正在寻找一些关于如何处理我在为客户编写的某些软件时遇到的问题的建议。简而言之,客户有一个第三方文件管理系统来存储他们的运输订单、发票等。当创建新文件时,客户需要保存和打印副本,以便与他们销售的商品一起发货。第三方软件制造商制作了一个带有 .NET DLL 的 SDK,它允许 C# 和 VB.NET 程序查询和保存文档。所以我给他们写了一个程序,使用这个DLL定期扫描系统,当它找到新文件时,我的程序会将它们保存到一个临时目录并打印出来。一切都很好,除了 SDK 做得不是很好,所以每当调用保存文档的方法时,一堆东西被加载到第三方 SDK 没有摆脱的 RAM 中(即它没有'不能很好地管理内存)。有时客户端会运行大批量,这种 RAM 的积累会减慢他们的系统,并导致几次内存不足异常。我写了一些示例代码来模拟这个问题。这确实需要一点想象力,但它会让你很好地了解我必须克服的问题。第一个代码示例模拟了第三方 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,其中包含一个与上面类似的类,并且您有一个全天候运行的程序,该程序会定期创建该类的实例。您将如何解决对象在内存中逐渐积累的问题。任何建议/建议将不胜感激。

谢谢!

【问题讨论】:

  • 假设没有维护强引用(也就是说,我们可以“信任”OtherGuysDllClass)那么就不会有问题 - 并且.NET会正确地回收(对自己)记忆。 (“GC.Collect 没有多大帮助”这一事实表明某些对象是不可可回收的;正确实施的流将最终完成本机资源。)但是,有错误的代码,所以 iff 就是这种情况.. 创建一个新的应用程序域并让应用程序“软重启”本身? (您也可以调用另一个可重新启动的应用程序域,以不影响主应用程序。)
  • 碰巧“其他人的DLL”实现了IDisposable?也许它不是错误代码,但您的代码没有清理它应该清理的东西......?
  • 大声笑!好吧,我不知道我们是否可以“信任”其他人的班级,但我别无选择。他们不会很快开始使用不同的 ECM 软件,所以我想我必须这样做。
  • @KevinHerrick 我使用过商业(和“支持”)库,我必须在单独的应用程序域中运行它们并帮助他们“洗衣服”。这很烦人,但可行。 (另外,我想说的是,让长时间运行的进程有一些回收任务的方法比没有更常见 - 分配一个全新的“进程”,带有新的石板和内存池。)
  • 考虑下载 ANTS 内存分析器以确认哪些对象挂在周围以及它们的根。解决内存问题的第一步是确定周围有什么。

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


【解决方案1】:

我认为新的 AppDomain 就是金钱! ++用户2864740

正如您所说,这种方法有点像 RubeGoldberg,但它可以完成工作。它执行速度足够快,并且内存使用保持稳定。

这是我修改后的解决方案。该项目包含对我要替换包含内存泄漏的第三方 .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 Event 以处理每次滴答时在新域中创建新域实例类:

包含 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 角度的建议。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-27
    相关资源
    最近更新 更多