我最近调试了一些有点内存泄漏的代码。这是一个长期运行的程序,作为Windows服务运行。
如果你发现一个人穿着IDisposable
界面,它告诉你它使用的一些资源超出了垃圾收集器为你清理的能力。
它告诉您的原因是您(此对象的用户)现在负责清理这些资源的时间。恭喜!
作为一名尽职尽责的开发人员,当您完成对象以释放这些非托管资源时,您会轻松调用.Dispose()
方法。
完成后,有一个很好的using()
模式可以帮助清理这些资源。只是发现哪些确切的物体导致泄漏?
为了帮助追踪这些流氓非托管资源,有没有什么方法可以查询在任何给定时间点等待处置的对象在游荡?
答案 0 :(得分:6)
不应该有任何你不想调用Dispose的情况,但编译器不能告诉你哪里你应该调用dispose。
假设您编写了一个创建并返回一次性对象的工厂类。如果清理应该是您的呼叫者的责任,那么编译器是否会因为没有调用Dispose而对您造成错误?
答案 1 :(得分:4)
IDisposable更多的是使用using关键字。它不是强迫你调用Dispose() - 它可以让你以一种光滑,非突兀的方式调用它:
class A : IDisposable {}
/// stuff
using(var a = new A()) {
a.method1();
}
离开使用块后,为您调用Dispose()。
答案 2 :(得分:3)
“有没有办法在程序结束时检测哪些对象在等待处理?”
好吧,如果一切顺利,在程序结束时,CLR将调用所有对象的终结器,如果IDisposable模式正确实现,将调用Dispose()方法。所以最后,一切都会被正确清理。
问题是,如果你有一个长时间运行的程序,你的一些IDiposable实例可能会锁定一些不应该被锁定的资源。对于这样的情况,用户代码应该使用using块或者在对象完成后立即调用Dispose(),但除了代码作者之外,除了代码之外的任何人都没有办法知道。
答案 3 :(得分:1)
您无需调用Dispose方法。实现IDisposable接口提醒您,您的类可能正在使用需要关闭的数据库连接,文件句柄等资源,因此GC是不够的。
AFAIK的最佳实践是调用Dispose甚至更好,将对象放在using
语句中。
答案 4 :(得分:1)
一个很好的例子是.NET 2.0 Ping类,它以异步方式运行。除非它抛出异常,否则在回调方法之前实际上不会调用Dispose。请注意,由于Ping实现IDisposable接口的方式,此示例有一些有点奇怪的转换,但也继承了Dispose()(并且只有前者按预期工作)。
private void Refresh( Object sender, EventArgs args )
{
Ping ping = null;
try
{
ping = new Ping();
ping.PingCompleted += PingComplete;
ping.SendAsync( defaultHost, null );
}
catch ( Exception )
{
( (IDisposable)ping ).Dispose();
this.isAlive = false;
}
}
private void PingComplete( Object sender, PingCompletedEventArgs args )
{
this.isAlive = ( args.Error == null && args.Reply.Status == IPStatus.Success );
( (IDisposable)sender ).Dispose();
}
答案 5 :(得分:1)
我可以问你如何确定它是专门实现IDisposable的对象吗?根据我的经验,最可能的僵尸对象是没有正确删除所有事件处理程序的对象(从而在另一个'live'对象中留下对它们的引用,并且在垃圾收集期间不将它们限定为无法访问)。
有些工具可以通过获取托管堆和堆栈的快照来帮助跟踪这些工具,并允许您查看在给定时间点使用的对象。免费赠品是使用sos.dll的windbg;它需要一些谷歌搜索教程,以显示您需要的命令 - 但它的工作原理,它是免费的。 Red Gate的ANTS Profiler在内存分析模式下运行,更加用户友好(不要与“简单”混淆)选项 - 它是一个灵活的工具。
编辑:关于调用Dispose的有用性 - 它提供了一种清除对象的确定方法。垃圾收集仅在您的应用程序用完已分配的内存时运行 - 这是一项昂贵的任务,它基本上会阻止您的应用程序执行并查看存在的所有对象并构建一个“可到达”(使用中)对象的树,然后清理无法到达的对象。在GC必须运行之前,手动清理对象会释放它。
答案 6 :(得分:0)
因为创建一次性对象的方法可能合法地将其作为值返回,也就是说,编译器无法告诉编程打算如何使用它。
答案 7 :(得分:0)
如果在一个类/模块(比如工厂)中创建一次性对象并将其移交给不同的类/模块以便在处理之前使用一段时间后该怎么办?该用例应该没问题,编译器不应该为此烦恼。我怀疑这就是为什么没有编译时警告---编译器认为Dispose
调用是在另一个文件中。
答案 8 :(得分:0)
确定何时何地调用Dispose()是一个非常主观的事情,取决于程序的性质以及它如何使用一次性对象。主观问题不是编译器非常擅长的。相反,这更像是静态分析的工作,静态分析是FxCop和StyleCop等工具的舞台,也可能是更高级的编译器,如Spec#/ Sing#。静态分析使用规则来确定是否满足主观要求,例如“始终确保.Dispose()在某个时刻被调用”。
老实说,我不确定是否存在能够检查是否调用.Dispose()的静态分析器。即使对于今天存在的静态分析,这可能有点过于主观的一面。但是,如果你需要一个开始寻找的地方,“静态分析C#”可能是最好的地方。