C# 我如何找到我的位置';我一直在泄露可识别的对象?

C# 我如何找到我的位置';我一直在泄露可识别的对象?,c#,C#,我最近一直在调试一些内存有点泄漏的代码。它是一个作为Windows服务运行的长时间运行的程序 如果您发现一个类带有IDisposable接口,它会告诉您它使用的一些资源超出了垃圾收集器为您清理的能力 它告诉您这一点的原因是,您,这个对象的用户,现在负责清理这些资源的时间。恭喜你 作为一名尽职尽责的开发人员,当您使用完对象以释放那些非托管资源时,您会被推动调用.Dispose()方法 有一种很好的使用()模式,可以在完成这些资源后帮助清理它们。只剩下找出导致泄漏的确切物体了 为了帮助追踪这些非法的

我最近一直在调试一些内存有点泄漏的代码。它是一个作为Windows服务运行的长时间运行的程序

如果您发现一个类带有
IDisposable
接口,它会告诉您它使用的一些资源超出了垃圾收集器为您清理的能力

它告诉您这一点的原因是,您,这个对象的用户,现在负责清理这些资源的时间。恭喜你

作为一名尽职尽责的开发人员,当您使用完对象以释放那些非托管资源时,您会被推动调用
.Dispose()
方法

有一种很好的
使用()
模式,可以在完成这些资源后帮助清理它们。只剩下找出导致泄漏的确切物体了


为了帮助追踪这些非法的非托管资源,是否有任何方法可以查询在任何给定时间点游荡等待处置的对象?

您无需调用Dispose方法。实现IDisposable接口提醒您,您的类可能正在使用需要关闭的资源,例如数据库连接、文件句柄,因此GC是不够的。
AFAIK的最佳实践是调用Dispose,或者更好的做法是,使用
语句将对象放入
中。

IDisposable更适合使用using关键字。它不是为了强迫您调用Dispose()-它是为了让您能够以一种巧妙、不突兀的方式调用它:

class A : IDisposable {}

/// stuff

using(var a = new A()) {
  a.method1();
}

离开using块后,将为您调用Dispose()。

因为创建一次性对象的方法可能会合法地将其作为值返回,也就是说,编译器无法判断编程打算如何使用它。

在任何情况下,您都不希望调用Dispose,但是编译器无法告诉您应该在哪里调用dispose


假设您编写了一个创建并返回一次性对象的工厂类。当清除应由调用者负责时,编译器是否会因为没有调用Dispose而对您造成错误?

一个很好的例子是.NET 2.0 Ping类,它异步运行。除非它抛出异常,否则在回调方法调用之前,您实际上不会调用Dispose。请注意,由于Ping实现IDisposable接口的方式,这个示例有一些奇怪的强制转换,但也继承了Dispose()(只有前者按预期工作)


如果一次性对象是在一个类/模块(比如工厂)中创建的,并在处置之前交给另一个类/模块使用一段时间,该怎么办?这个用例应该是可以的,编译器不应该纠缠你。我怀疑这就是为什么没有编译时警告的原因——编译器假定
Dispose
调用在另一个文件中。

“有没有办法在程序结束时检测哪些对象正在四处游荡,等待处理?”

如果一切顺利,在程序结束时,CLR将调用所有对象的终结器,如果IDisposable模式得到正确实现,它将调用Dispose()方法。因此,在最后,一切都将得到妥善清理


问题是,如果您有一个长时间运行的程序,那么您的一些IDiposable实例很可能正在锁定一些不应该锁定的资源。对于这种情况,用户代码应该在处理对象后立即使用using block或call Dispose(),但是除了代码作者之外,任何人都无法知道这一点。

确定何时何地调用Dispose()是一件非常主观的事情,这取决于程序的性质以及它如何使用一次性对象。主观问题不是编译器擅长的。相反,这更像是静态分析的工作,它是FxCop和StyleCop等工具的竞技场,或者是Spec#/Sing#等更高级的编译器的竞技场。静态分析使用规则来确定是否满足主观需求,例如“始终确保在某个点调用.Dispose()”


老实说,我不确定是否存在能够检查是否调用了.Dispose()的静态分析器。即使对于今天存在的静态分析,这也可能有点过于主观。但是,如果您需要一个地方开始寻找,那么“C#的静态分析”可能是最好的地方。

我可以问您如何确定实现IDisposable的是特定的对象?在我的经验中,最有可能的僵尸对象是那些没有正确删除所有事件处理程序的对象(从而留下来自另一个“活动”对象的对它们的引用,并且不会在垃圾收集期间将它们限定为不可访问)

有一些工具可以通过拍摄托管堆和堆栈的快照并允许您查看在给定时间点被认为在使用的对象来帮助跟踪这些对象。免费赠品是使用sos.dll的windbg;这需要一些谷歌教程来向你展示你需要的命令——但它是有效的,而且是免费的。一个更为用户友好的选项(不要将其与“简单”混淆)是Red Gate的ANTS Profiler,它运行在内存分析模式下——这是一个灵活的工具


编辑:关于调用Dispose的有用性——它提供了一种清理对象的确定性方法。垃圾收集仅在应用程序的分配内存用完时运行——这是一项昂贵的任务,它基本上会停止应用程序的执行,并查看所有存在的对象,构建一个“可访问”(正在使用)对象树,然后清除不可访问的对象。手动清理对象会在GC必须运行之前释放它。

或者如果一次性对象被设置到属性/字段或传递到任何其他方法中。如果IDisposable对象仅在单个方法的生存期内存在
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();
}