我刚开始学习编程。我正在学习约翰夏普的《Microsoft Visual C#逐步学习指南(第9版)》第14章,但有些地方不太懂。
作者写道:
……finalizer(终结器)可以在对象的最后一个引用消失后任何时候运行。因此,如果Dispose方法需要进行大量工作,垃圾回收器可能会在其自己的线程上调用终结器,尤其是在Dispose方法运行时。
1) 在这里,我有一个第一个问题,这如何可能?毕竟,当没有更多链接时,GC CLR会正确地销毁对象。但如果没有引用,那么如何同时仍然可以运行一个(Dispose())方法,没有别的东西与它相关联?是的,没有链接,但方法没有完成,GC CLR将尝试删除仍在运行的方法对象吗?
此外,作者建议使用lock(this)来避免这个问题(并行调用Dispose()),并进一步说明这可能对性能产生不利影响,立即提出了本章前面描述的另一种策略。
class Example : IDisposable
{
private Resource scarce; // scarce resource to manage and dispose
private bool disposed = false; // flag to indicate whether the resource
// has already been disposed
...
~Example()
{
this.Dispose(false);
}
public virtual void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
// release large, managed resource here
...
}
// release unmanaged resources here
...
this.disposed = true;
}
}
//other methods
}
2) 我理解它的工作原理以及为什么需要,但是我遇到了一个问题,就是如何准确地提到 this.Dispose(true)
和 this.Dispose(false)
的并行执行。
在提出的最终解决方案中,哪种方法将阻止GC CLR通过析构函数在其线程中与以前一样并行调用this.Dispose(false)
方法,而this.Dispose(true)
仍将在之前显式启动时执行?
乍一看,这类似于GC.SuppressFinalize
(this) 构造,但它应该仅在结束this.Dispose(true)
后才能起作用,并且在条件允许的情况下,它会长时间地在GC CLR的线程中执行,并启动析构函数和this.Dispose(false)
。
据我所知,没有任何防止的措施,我们只获得了取消非托管资源(文件、数据库连接等)的重复,但我们不会得到管理资源(例如大型多维数组)的重复。
这意味着可以重复释放非托管资源,却不能重复释放托管资源吗?这比使用锁定 (this) 构造更好吗?
Dispose()
方法,则this
仍然是arg0
参数,即使在其他任何地方都没有出现 - 因此引用确实存在。 我假设作者是在谈论某种内联调用的情况,但对于这种情况有用,我们还需要假设它在dispose期间不与任何字段交互,因为字段也引用了这个需要被保留的this
; 所以... 这听起来像一个极其假设的情况,实际上不能有任何可观察的副作用! - Marc GravellDispose()
的线程中吗?它们确实是顺序执行的;然而,GC 有一个单独的线程,可以做任何它想做的事情。但是:如果您没有处理非托管数据(通常是指针):这些都不适用,您很可能会使事情过于复杂化。该死的,如果您没有非托管数据,甚至不应该拥有终结器。 - Marc Gravelllock
不是一个好主意。幸运的是,隐含的Monitor.Exit
将与GC.KeepAlive
执行相同的功能,并且将防止其被收集,因为如果您曾经在 GC 线程中成功地触发了lock
(Monitor.[Try]Enter
),那么您已经完全破坏了应用程序(它永远无法解锁)。所以它可以工作,但原因都是错误的! - Marc Gravell