为什么c#/ xna中的垃圾收集不会自动处理渲染目标?

Dan*_*ter 1 c# xna garbage-collection

我发现c#/ xna中的渲染目标不会被自动处理掉,你必须调用.dispose()成员去除它们.

我认为垃圾收集是假设在所有引用都消失后自动摆脱的东西,是什么给出的?

还有什么不是自动处理的吗?

Jon*_*nna 10

我认为垃圾收集是假设在所有引用都消失后自动摆脱的东西,是什么给出的?

这在两个方面是不正确的.

垃圾收集收集可安全收集的托管内存,并且是必需的.而已.

将垃圾收集视为模拟无限堆内存的方法.既然我们可以假装我们拥有无限的记忆,那么我们永远不必调用任何东西来释放我们已经完成的内存,因为为什么我们会保留无限的资源呢?*

GC模拟无限堆的最简单方法是什么都不做.这适用于例如该进程具有4GiB内存可用,并且它使用50MiB.收集永远不会出现.如果应用程序足够小,集合永远不会发生,确实会发生这种情况.(虽然它不是那么懒,让你在没有集合的情况下使用megs,它会尝试收集,然后才会尝试向操作系统寻求更多内存的应用程序,当你想知道"为什么没有GC"时它仍然很有用. ......"无所事事在某些时候可能是一种有效的GC方法,一旦你有了这种可能性,很多其他问题就会消失".

另一种方法是在清理所有东西时急切地清理所有东西.参考计数垃圾收集器会发生这种情况; 这与.NET无关,但值得一提,因为它与你的问题"毕竟他们的引用已经消失"非常匹配.

另一个是当需要内存,并且在已经空闲的内存存储中不可用时,GC会停止所有线程,识别根(静态变量和每个线程的堆栈中的本地引用),标识根引用的所有内容,以及那些引用的所有东西,依此类推,直到找到应用程序仍然可以使用的所有东西,然后将其他所有内存都视为空闲.然后它压缩所有没有释放内存的对象,这既避免了在释放更多空闲内存时的碎片,也经常使对象在内存中保持更紧密(这具有较小的性能优势).

如果还"提升"它没有删除的对象,因为如果第一次删除它的可能性不大,那么下一次就不会删除它,因此它会查看那些在此过程中幸存下来的对象经常.

有两点需要注意:

  1. 我们无法预测GC何时会释放给定对象的内存.
  2. GC唯一能做的就是免费管理内存.它没有做任何其他事情(它确实有助于终结者,我们将在稍后介绍).

获取然后释放托管内存当然只是我们可能希望首先执行某些操作然后撤消它的一种情况.其他例子是:

  1. 获取文件句柄并释放它.
  2. 获取窗口句柄,然后释放它.
  3. 获取GDI句柄,然后释放它.
  4. 打开网络连接,然后关闭它.
  5. 通过网络连接发送协议定义的握手,然后在关闭之前在其上发送协议定义的签名.
  6. 获取一些非托管内存,然后释放它.
  7. 获取一个对象(除了分配给它的创建之外还有一些开销,或者它通过使用"学习")从池中获取,然后将其返回到池中.

我们已经描述过它的GC到目前为止对这些都没有帮助.

不过,它们都有两个共同的特征.它们具有启动操作和结束操作.

启动操作将很好地映射到对象创建,或者映射到某些方法调用.

的结束动作可以匹配到Close(),End(),Free(),Release()方法调用,但在定义IDisposable.Dispose()我们可以给他们所有的通用接口.语言也可以通过using† 增加一些帮助.

(一个类可能同时具有a Close()和a Dispose().在这种情况下,我们既可以选择关闭我们稍后将重新打开或以其他方式使用的关闭状态,也可以选择在我们完成之后保证清理的方法.宾语).

因此,以这种方式,IDisposable.Dispose()存在清理所有需要清理的东西,除了托管内存.

现在,在这种情况下,有三种类需要实现IDisposable:

  1. 那些拥有像手柄一样的非托管资源的人.
  2. 那些我们正在使用某种池化或其他前后方案(我们自己设计的方案)(或其他人的设计,但仍然在.NET本身内).
  3. 那些有字段反过来实现的IDisposable,因此当我们处理这个类的对象时,它会调用Dispose()那些字段.

让我们考虑如果GC释放这样一个对象的内存并且Dispose()没有被调用会发生什么.

在第三种情况下,实际上并不重要的是,物体没有被处理掉.真正重要的是这些田地没有被处理掉(或者也许并不重要,但是有些领域很重要).

在第二种情况下,重要的是取决于汇集的重要性.它可能是次优的,但不是世界末日.

在第一种情况下,这是一场灾难 - 我们有一个未发布的资源,在应用程序结束之前我们不可能发布,甚至可能在此之后(取决于资源的性质).

出于这个原因,对象可以有终结者.

当GC即将释放对象的内存时,如果它有一个终结器,并且该终结器还没有被抑制(Dispose()通常这样做是为了表明该对象已被很好地清理,并且不再需要它的工作),然后不是从对象释放内存,而是将其放入终结队列.这当然意味着该对象不仅没有收集其内存,而且也没有通过其字段可达到的内容.

终结者线程在此队列中运行,在每个队列上调用finaliser方法.

这件事发生了两件坏事:

  1. 我们不知道什么时候会发生.也许我们会耗尽资源或者无法打开文件进行编写.
  2. 这意味着应该已经释放了应该释放其内存的对象,并且它不仅会生存一个比它应有的更长的循环,而是会延长很多循环.

编辑:请注意,我们没有第三类的终结者,也许不是第二类.在这种情况下,不需要终结者,因为它作为一个领域的真正关键对象将被称为终结者,这是重要的工作.如果你试图在终结者中处理一个可终结的字段,那么最终也会很容易出现一个残暴的错误.如果你写一个包装一个或多个一次性领域的一次性类,它"拥有"并负责清理,然后实施IDisposable,但添加终结者.

总之,被称为终结者意味着两件事之一:

  1. 应用程序正在关闭,所有终结器都在运行(盛大,一切都与世界相吻合).
  2. 有人搞砸了,并没有清理他们应该有的东西.

因此,虽然通过终结器在GC和管理内存之外的资源之间存在交互,但这是最后的交互,绝不是可靠的.您不应该将终结器视为使GC进行清理的一种方法,但作为一种方法,如果缺陷意味着它没有发生,那么GC就不可能无法进行清理(以及一种清洁的方法)应用程序关闭时的特写).

*当然,如果你认为你拥有无限的资源(鱼,水牛,海洋的废物处理能力)而事实证明你没有,那么事情就会变得混乱,所以也许不要将这个规则应用于生活.

using使调用变得Dispose()更简单

using(someDisposableObject)
{
  //Do Stuff
}
Run Code Online (Sandbox Code Playgroud)

相当于:

try
{
  //Do Stuff
}
finally
{
  if(someDisposableObject != null)
    ((IDisposable)someDisposableObject).Dispose();
}
Run Code Online (Sandbox Code Playgroud)

和:

using(var someDisposableObject = someMethodCallOrCallToNew())
{
  //Do Stuff
}
Run Code Online (Sandbox Code Playgroud)

相当于:

var someDisposableObject = someMethodCallOrCallToNew();
try
{
  //Do Stuff
}
finally
{
  if(someDisposableObject != null)
    ((IDisposable)someDisposableObject).Dispose();
}
Run Code Online (Sandbox Code Playgroud)

如果编译器可以确定someDisposableObject不可能作为优化,则可以删除空检查.