【问题标题】:Which objects does the garbage collector not clean up?垃圾收集器不清理哪些对象?
【发布时间】:2010-11-23 21:16:50
【问题描述】:

静态分析工具不断告诉我我的 C# 代码中存在资源泄漏。

这是一个例子:

StringReader reader = new StringReader(...); 

// do something with reader

...

} // static analysis tool cries that I've leaked **reader**

我的工具正确吗?如果有,为什么?

编辑(回复评论)- 我的静态分析工具说我有一堆资源泄漏。我从forum 知道某些 Java AWT 对象需要显式释放,否则会发生泄漏。是否存在需要显式释放的 C# 对象?

【问题讨论】:

  • 什么是静态分析工具?你能说得更具体些吗

标签: c# garbage-collection


【解决方案1】:

是的,您的代码泄漏严重。应该是这样的:

using (StringReader reader = new StringReader(...))
{

}

实现IDisposable 的每个类都需要包装在using block 中,以确保始终调用Dispose 方法。


更新:

详细说明:在 .NET 中有定义 Dispose 方法的 IDisposable 接口。实现此接口的类(如文件流、数据库连接、读取器等)可能包含指向非托管资源的指针,确保释放这些非托管资源/句柄的唯一方法是调用 Dispose 方法。所以在 .NET 中要确保即使抛出异常也能调用某些代码是使用 try/finally 语句:

var myRes = new MyResource(); // where MyResource implements IDisposable
try
{
    myRes.DoSomething(); // this might throw an exception
}
finally
{
    if (myRes != null)
    {
        ((IDisposable)myRes).Dispose();
    }
}

编写 C# 代码的人很快意识到,每次处理一次性资源时编写此代码都是 PITA。所以他们引入了using 声明:

using (var myRes = new MyResource())
{
    myRes.DoSomething(); // this might throw an exception
}

这有点短。

【讨论】:

  • 您说它“严重”泄漏 - 但在 StringReader 的情况下,实际上什么都不会泄漏 - 在这方面就像 MemoryStream。无论如何,最好还是丢弃它,只是为了最佳实践,以防您更改代码以使用类似的东西确实需要清理......但它并没有真正泄漏。跨度>
  • @Jon,是的,但我们在这里教授良好的做法。人们会说这没问题,我讨厌看到这样的代码:var writer = new StreamWriter("foo.txt")。
  • @Mike Caron,是的,稍后,您是否尝试过处理文件?你有没有遇到过:This file cannot be opened because it has been used by an other process 异常?请在每次处理 IDisposable 资源时使用using。既然生活很容易,我们为什么要让生活变得更艰难?为什么我们要问所有这些问题?世界上没有任何事情能让我相信StringReader reader = new StringReader(...); 是好的代码。如果微软在 C# 9.0 中在 StringReader 中引入一些新概念怎么办?那么代码实际上可能会出现问题并中断。
  • @Darin:我相信传授良好实践包括诚实——特别是,如果你告诉某人他们的代码“严重泄漏”,而他们决定是否“严重泄漏”是在这种情况下不要伤害他们,也许在其他时候不会伤害他们,你在自找麻烦。最好说实话,但也提供良好的做法,IMO。
  • 希望啤酒不会漏水。现在这是一个真正的问题
【解决方案2】:

在这种特定情况下,您的代码实际上不会泄漏任何东西,因为 StringReader 没有任何资源需要清理,就像 MemoryStream 没有。 (使用MemoryStream,如果您异步使用它或远程处理它,您可能仍然需要处理它......但在简单的情况下它并不重要。)

但是,原则上处置任何实现IDisposable 的东西是个好主意。这将避免您泄漏(可能是暂时的)非托管资源,例如文件句柄。

例如,假设您将代码更改为:

StreamReader reader = new StreamReader("file.txt");
...

如果您没有在此处关闭或处置reader(在finally 块中或通过using statement),它将保持文件打开,直到最终确定直接持有操作系统文件句柄的任何类型。显式处理事物不仅可以更早地释放非托管资源 - 它还可以将可终结对象从终结队列中移除,这意味着它们可以更早地被垃圾回收。

【讨论】:

    【解决方案3】:

    看起来你没有处理你的 StringReader。您需要调用.Dispose() 来清理非托管资源。或者更好的是,在 using 块中使用它:

    using (StringReader reader = new StringReader(...))
    {
        // your code
    }
    

    这将在块的末尾自动导致 Dispose(),即使您的代码抛出异常(与使用 finally 块相同)。

    【讨论】:

      【解决方案4】:

      正如其他人所说,它归结为 StringReader 没有被处置,所以我不会打那个。

      静态分析工具本质上是一个愚蠢的工具。我并不是说不要使用它,我的意思是愚蠢,因为它正在查看一个非常有限的标准。

      在这种情况下,它看到一个正在实例化的对象,其类实现了 IDisposable。然后,该工具只是查看您是否在对象超出范围之前进行了相应的 dispose 调用。这可以通过明确地说 object.Dispose();或通过 using(var x = ...) { } 子句。

      根据MS specs,类在处理非托管资源(如文件句柄)时应实现 IDisposable。现在,您可能想要查看此MSDN post,其中讨论了哪些实现了 IDisposable 的类,您不一定必须调用 dispose()。

      这给我们留下了两个可行的解决方案。第一个(也是我和 Darin 推荐的一个)是始终将实现 IDisposable 的对象包装在 using 子句中。这只是一个好习惯。毕竟,它会导致零伤害,而没有它可能会导致大量内存泄漏(取决于类),我not smart enough 记住哪个是哪个。

      另一种方法是配置您的静态分析工具(如果可能)以忽略此类警告。我真的认为这将是一个坏主意(tm)

      【讨论】:

        【解决方案5】:

        有许多类的流,以及相关的阅读器对象类型。其中一些类以一种必须在完全放弃之前撤消的方式操作外部对象,但在仍然需要它们时无法撤消(例如,文件阅读器将打开文件;文件必须在文件句柄之前关闭被遗忘,但在读者完成之前不能关闭)。 “Dispose”方法将处理在完全放弃之前必须由读者“放回”的任何内容。因为一段代码创建流阅读器并将其交给另一段代码是很常见的,而且因为第一段代码可能无法知道使用阅读器的代码何时完成,代码使用阅读器有责任在阅读器完成后调用 Dispose。

        请注意,某些类型的读取器实际上并没有在其 Dispose 方法中执行任何操作,但任何接受任意类型的流读取器的代码都应该调用它。虽然您的代码可能需要一种不需要 dispose 的流阅读器,但它可能会被赋予一个确实需要它的派生类。即使在并非绝对必要时调用 Dispose 也可以防止出现此类情况。

        【讨论】:

          【解决方案6】:

          我相信这是因为你在完成之后还没有调用Close 方法,尽管根据 MSDN:

          此 Close 实现调用 Dispose 方法并传递一个真值。

          所以我希望当垃圾收集器到达阅读器时,它会以相同的最终结果被处置(并且没有内存泄漏)。

          UPDATE:我弄错了,GC 不会自动 Dispose IDisposable 对象。您需要显式调用 Close(或 Dispose)。

          【讨论】:

          • 如果IDisposable模式被正确实现,那么类终结器(~Class)将运行,应该与调用Dispose具有相同的效果。当然,还不止这些,我这里没有足够的空间来解释。
          【解决方案7】:

          您已经泄漏,但“最终”GC 会为您清理它。

          【讨论】:

            【解决方案8】:

            字符串读取器可能正在访问流。通过不处理 stringreader,您可能会使该流保持打开状态。流可以附加到系统上的文件,然后您可能已将其锁定。

            尝试查看 using 语句,它会自动为您调用 dispose。

            using (sr) {
             // your code
            }
            

            【讨论】:

              【解决方案9】:

              垃圾收集器将收集不再引用它的任何内容。在您的示例中,reader 最终将被收集(尽管没有人知道何时)。

              但是,“静态分析工具”抱怨您没有手动调用 Dispose()。

              StringReader reader = ...
              ...
              reader.Dispose();
              

              在这种特定情况下,这可能没什么大不了的。但是,在处理许多 IO 类(*Stream、*Reader 等)时,最好在完成后将它们处理掉。您可以使用using 提供帮助:

              using(StringReader reader = ...) {
                  ...
              }  //reader is automatically disposed here
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2020-05-08
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-04-10
                • 2023-03-12
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多