【问题标题】:How GC manages any class/object like streamwriter which implement IDisposable by default in case of calling Dispose method or not ? [closed]在调用 Dispose 方法的情况下,GC 如何管理默认实现 IDisposable 的任何类/对象(如 streamwriter)? [关闭]
【发布时间】:2016-04-14 20:27:32
【问题描述】:

我读过一些关于 GC、Finalizers、Managed & Unmanaged Objects、Disposable pattern @StackOverflow 的文章。

目前,我对正确使用 GC、终结器、一次性模式和托管、非托管资源术语感到很困惑。

恕我直言,关于上述主题有很多误导性的答案。

例如;

我以this的帖子为例

这个问题的公认答案意味着,如果我们不调用默认实现 IDisposable 接口的 .net 对象的 dispose 方法,我们将无法释放非托管资源。

我认为这是一个错误的说法。首先在托管资源和非托管资源之间存在概念混淆。在我看来,

托管资源:任何实现 IDisposable 接口的 .NET 类,例如 Streams 和 DbConnections。

非托管资源:封装在托管资源类中的填充物。 Windows 句柄是最简单的例子。

正如@kicsit 在this 帖子中所说的那样

所以我最终知道所有默认实现 IDisposable 接口的类(例如 pen、streamwriter)都是托管资源 包括非托管资源。

显式调用 IDispose 方法和让 GC 执行的所有区别在于,一个间接地向 GC 发出信号,表明可以在下一次 GC 期间清理对象,后者是完全不确定的。

但是,当我查看 Dataset 和 Datatable 类时,尽管它们默认实现 IDisposable,但它们内部不拥有任何非托管资源。 This接受的答案也支持我的想法。

引自回答

system.data 命名空间 (ADONET) 不包含非托管资源。 因此,只要您没有为自己添加一些特殊的东西,就不需要处理任何这些。


所以我的第一个想法失败了。默认情况下拥有 Disposable 并不一定意味着类/对象内部有非托管资源。

Q1)是真的吗?

Q2) 如果我使用像 streamwriter 这样默认包含 IDisposable 接口的类而不调用它。GC 会将其放入 Finalization 队列然后分别调用 Dispose 方法吗?(我的意思是 StreamWriter.Dispose() 方法是相当于 StreamWriter.Close()

Q3) 如果我们显式实现析构函数,是否会出现终结队列?

【问题讨论】:

标签: c# garbage-collection dispose idisposable finalizer


【解决方案1】:

我认为您在这里试图区分托管和非托管资源让您感到困惑。

让我试着想象一下这样的事情:

你有一个对象,它代表一个资源(无论是连接、句柄、任何东西,无论是否托管)。假设它是IDisposable,并且还有一个终结器。

您在代码中使用该对象,并且在某些时候您已经完成了它。

  • 如果你调用Dispose 方法,你基本上是在告诉对象你已经完成了它,并指示它释放它持有的任何资源(销毁句柄,关闭连接等等)。

    这应该是首选的行动方案,最好通过using 语句完成。

    此时,正确实现的 dispose 模式会将对象标记为不再需要完成。

  • 您忽略了调用Dispose 并让对象无法访问。在某些时候,它被 GC 发现,GC 将对象放入终结器队列,终结器运行并释放资源(稍后会详细介绍),您的对象返回 GC,然后被释放。

    这大约需要一个世纪。在此期间,您有一个未引用的对象无缘无故地持有一些资源。而且你通过强制它完成对象来给 GC 施加压力。

    将此视为一种故障安全机制。 GC 完成这项肮脏工作的事实可以让您避免内存/资源泄漏,因为您的资源最终会被释放。但是为什么要等待并给系统施加压力呢?

现在,您将了解到终结器用于释放非托管资源,这是正确的。这是为什么?您不需要需要释放托管对象,因为 GC 可以处理这些。这是传递性的:如果您有一个托管对象链,其中最后一个对象持有一个非托管引用,那么只有最后一个对象需要特别注意,而 GC 最终会处理它。

有时,当您超出using 范围时,一次性模式会用于执行副作用,无论是正常情况还是通过异常传播。这是一个很好的技术,用于确定性清理。
但是你不能依赖终结器来执行这样的副作用。一方面,它是从终结器线程中不确定地调用的,更糟糕​​的是:you can't assume anything 关于它运行的环境。这就是为什么您应该只执行能够避免内存泄漏和释放终结器中的非托管资源的代码。在终结器中做最少的工作。


现在,让我们看看你的问题:

默认情况下拥有 Disposable 并不一定意味着类/对象内部具有非托管资源。是真的吗?

正确。

如果我使用像streamwriter这样默认包含IDisposable接口的类而不调用它。GC会将它放入Finalization队列然后分别调用Dispose方法吗?(我的意思是StreamWriter.Dispose()方法,相当于StreamWriter.Close ()

绝对不是。在这种情况下,将在可疑环境中调用终结器。 IDisposable.Dispose 方法将不会被调用。

但是有一个非常常见的模式,它是以下的变体:

class Foo : IDisposable
{
    ~Foo() => Dispose(false);
    public void Dispose() => Dispose(true);

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // Do whatever you want to perform deterministically
            GC.SuppressFinalize(this); // No need to finalize anymore
        }

        // Free any unmanaged resource you hold.
        // Make sure you don't throw here, or kiss your process goodbye.
    }
}

我猜你可以Dispose(bool) 方法在此处的最终确定中被调用,但不要将此与 IDisposable.Dispose 混淆。

如果我们显式实现析构函数,是否会出现终结队列?

是的,如果对象声明了一个析构函数,也就是一个终结器,它将被放置在终结队列中。我不喜欢 destructor 这个术语,因为它与 C++ 有一个 非常 不同的含义,最好将其称为终结器,这样区别就很明显了。

【讨论】:

  • 当且仅当显式终结器被实现时,您断言任何对象都将被放置在终结队列中。我也和你在同一点上。但是,那么 GC 如何调用终结器如果即使我没有明确实现终结器并且忽略调用 StreamWriter.Dispose()/Close()。据我所知,StreamWriter 保留非托管资源。因此它们必须以某种方式处理。它应该以某种方式执行 Dispose()/Close() 方法来释放非托管资源?但是如何?
  • StreamWriter 不需要非托管资源,它只包含对 Stream 的引用,而 Stream 又可能包含非托管资源,具体取决于流类型......有点,还有this helper class 如果用户选择启用MDA,它确实有一个终结器。顺便说一句,该链接包含StreamWriter 代码,请看一下。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-10
  • 1970-01-01
  • 2011-03-08
  • 1970-01-01
  • 2020-12-19
  • 2011-01-20
  • 1970-01-01
相关资源
最近更新 更多