我为这篇长篇文章提前道歉......
我正在寻找一些关于如何处理我遇到的问题的建议,这些问题是我为客户编写的一些软件。简而言之,客户有一个第三方文件管理系统,存储他们的装运单,发票等。创建新文件时,客户需要保存和打印的副本,以便与他们销售的商品一起发货。第三方软件制造商生成一个带有.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,并且您有一个全天候运行的程序,可以定期创建该类的实例。你如何解决在内存中逐渐积累物体的问题。任何建议/建议将不胜感激。
谢谢!
答案 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角度。