【问题标题】:IDisposable with destructor: requires thread-safe implementation?带析构函数的 IDisposable:需要线程安全的实现?
【发布时间】:2011-01-17 08:36:02
【问题描述】:

这几乎只是为了确保我做对了:

我们有一个实现 IDisposal 模式的大型资源类。它应该(通过设计)以某种方式实现,使其能够被多次调用(即使我们尝试准确地调用它一次)。我们还实现了一个终结器,它也调用 Dispose() 方法 - 就像备份一样。如果手动调用,Dispose() 也会调用 GC.SuppressFinalize(this)。

周围有几个处置模式的例子。他们中的大多数在处理代码的 end 处调用 GC.SuppressFinalize(this)。有人声称,最好在 Dispose() 方法的开头调用它,任何清理之前。后者争辩说,这将确保 GC 在我们仍在清理时不会同时调用终结器。

问题:
看来,将 GC.SuppressFinalize 放在开头并没有更好的效果?我们还有比赛条件,对吧?那么,我们宁愿以线程安全的方式实现 Dispose() 是真的吗?

【问题讨论】:

  • dispose 模式,如 .NET 框架设计指南中所述,最后有 GC.SuppressFinalize 调用。该指南在 Microsoft 内部进行了深入讨论,最终形成了这样的模式。

标签: c# .net thread-safety dispose finalizer


【解决方案1】:

GC 只清理不“可达”的对象。

执行代码的类仍然是“可访问的”,因为它的this 指针在堆栈上。因此,在 dispose 执行时,不会调用终结器。

所以不管你在开始还是结束时调用SuppressFinalize

正如下面的评论者所指出的,CLR 实现似乎无法保证您的对象在实例方法执行时不会被垃圾收集/终结。使对象保持活动状态的唯一可能的“可靠”引用是用于调用对象上的方法的引用,但我对 JIT 内部的了解不够多,无法对此发表声明,而且它的行为可能会改变。

我将在此处留下答案,以便访问下面的讨论。

【讨论】:

  • 这不太准确,执行实例方法不会阻止最终确定。抖动为 this 的 GC 提供生命周期信息,就像它为任何局部变量或方法参数所做的那样。但是,是的,保证不会留下任何代码运行仍会触及对象。
  • @jdv-Jan de Vaan:大多数情况下可能都是这样。但是,至少存在一个例外:blogs.msdn.com/b/ricom/archive/2004/05/19/135332.aspx(在该网站上进一步讨论)-所以我想,我们至少应该牢记这一点并为那些(罕见的)情况做好准备。
  • @Hans Passant:所以你是说我错误地假设 this 指针在调用实例方法之前被压入堆栈?还是 CLR 在开始运行代码之前主动清除堆栈?我知道局部变量在最后一次引用后不会被 GC 考虑,但在这种情况下,指针是一个(隐式)函数参数。
  • @Hans Passant:“保证不会留下任何代码运行仍会触及对象。” - 除了完成代码,我想?
  • @user492238:如果你参考讨论如果你从终结器调用Dispose 会发生什么的部分,我认为这不是很好的行为。请参阅:msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.71).aspx:(“Finalize 方法不应引用任何其他对象。”)
【解决方案2】:

虽然有时可能会在存在看似有效的引用时完成对象,但只有在没有其他任何东西引用该对象时才会发生这种情况。 GC.SuppressFinalize(this) 主动引用当前对象“this”,从而保证在 GC.SuppressFinalize 执行之前它不会被最终确定。此外,对象引用存在以处置对象并且可用于 Dispose 方法的事实保证了终结器在 Dispose 开始运行之前不会排队,除非对象已死并且某个终结器(它自己的终结器) ,或其他物体的)复活它。

因为在某些情况下,对象可能会被安排完成并在不知不觉中复活,因此保护 dispose 和 finalize 免受冗余操作可能不是一个坏主意。然而,微软的模式并不是一个好的模式。可终结对象不应包含对终结不需要的任何对象的引用。如果一个对象混合了托管资源和非托管资源,则应该将非托管资源移到它们自己的类中(有效地将它们变成托管资源),这样主对象就只包含托管资源。

【讨论】:

    【解决方案3】:

    Dispose 不应抛出任何异常。

    我会确保 Dispose 中的所有代码都是线程安全的,这样如果它被调用,它就不会做任何奇怪的事情。通常添加检查变量是否已经为空应该可以解决问题。

    在微软的例子中,我只在 Dispose 函数的末尾看到了 GC.SuppressFinalize。

    【讨论】:

      猜你喜欢
      • 2013-10-03
      • 1970-01-01
      • 2010-10-13
      • 2014-02-11
      • 1970-01-01
      • 1970-01-01
      • 2014-02-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多