C#/ XNA - 将对象加载到内存 - 它是如何工作的?

car*_*arl 7 c# memory xna

我从C#和XNA开始.在"Game"类的"Update"方法中,我有以下代码:

t = Texture2D.FromFile( [...] ); //t is a 'Texture2D t;'
Run Code Online (Sandbox Code Playgroud)

加载小图像."Update"方法的工作方式类似于循环,因此这段代码会在一秒钟内多次调用.现在,当我运行我的游戏时,它需要95MB的内存并且缓慢增加到大约130MB(由于我发布的代码,没有这个代码,它仍然保持在95MB),然后立即进入大约100MB(garbare集合?)并再次缓慢到130MB,然后立即到100MB,依此类推.所以我的第一个问题:

  1. 你能解释为什么(怎么样)它的作用?

我发现,如果我将代码更改为:

t.Dispose()
t = Texture2D.FromFile( [...] );
Run Code Online (Sandbox Code Playgroud)

它的工作方式是这样的:首先它需要95MB然后缓慢到大约101MB(由于代码)并保持在这个级别.

  1. 我不明白为什么需要这个6MB(101-95)...?

  2. 我想让它像这样工作:加载图像,从内存释放,加载图像,从内存释放等等,所以程序应该总是需要95MB(在以前的方法中只加载一次图像需要95MB).我应该使用什么说明书?

如果重要,图像的大小约为10KB.

谢谢!

And*_*ell 13

首先,你需要明白你正在做的事情很奇怪!

加载纹理的"常规"方式是使用内容管道和内容管理器.

如果你真的必须从文件而不是内容系统加载纹理 - 你应该只对每个纹理执行一次.您应该使用Texture2D.FromFilein Game.LoadContent和call Disposein Game.UnloadContent(通常内容管理器会处理为您调用Dispose - 但是因为您没有通过内容管理器,所以您必须自己调用Dispose).


要意识到的重要一点是,您在此处使用托管和非托管资源.

托管对象使用的内存将由垃圾收集器处理 - 在这种情况下,Texture2D的每个实例都使用一部分托管内存.99%的时间你不需要担心它 - 垃圾收集器真的善于处理托管内存 - 这是它的工作!

什么,你需要担心的是托管资源-在这种情况下,正在使用的所有这些纹理你载入纹理内存.最终垃圾收集器将运行,查看所有这些未引用的Texture2D对象并收集它们.当它收集它们时,它将最终确定它们,就像调用Dispose一样,它将释放非托管资源.

但垃圾收集器无法"看到"这些Texture2D对象正在使用的所有非托管资源.它不知道何时需要释放这些物体 - 你可以自己做得更好.您需要做的就是Dispose在不再需要纹理时调用它们.

这是因为垃圾收集器不知道纹理使用的30MB额外内存,它是在您不调用Dispose时累积的.除此之外,当你查看进程内存使用时你看不到的是GPU上那些纹理正在使用的所有内存!

(Texture2D的底层实现是它拥有对DirectX纹理对象的引用 - 它本身是一个使用非托管主内存的非托管对象 - 它反过来处理GPU上的纹理及其相关内存.Grace2D对象的托管部分只有大约100-200个字节 - 它存储上述参考和纹理宽度,高度,格式等的缓存.)


现在 - 至于你想要实现的目标.如果你真的需要每帧加载纹理(这是非常不寻常的),那么你也不再需要以前的纹理......

好吧,首先,Dispose每帧调用未使用的纹理一种有效的方法.您将依赖垃圾收集器来清理它创建的所有垃圾(Texture2D对象的托管端) - 这在Windows上很好但可能会破坏您在Xbox上的性能.至少你不会泄漏非托管资源.

一个更好的方法,特别是如果纹理每次都是相同的大小,只是继续使用相同的纹理对象.然后Texture2D.SetData用每帧的新纹理数据调用它.

(如果您的纹理大小不同,或者您在给定时间使用多个纹理,则可能需要实现类似纹理池的内容.)

当然,LoadFile采用实际文件,SetData采用原始数据.您必须自己实施数据转换.一个很好的起点可能是XNA论坛上的这个主题.


Mic*_*tum 3

更新方法不应该用于加载纹理,因为它加载了很多。在 .net 中,对象是垃圾收集的,这意味着您无法显式释放对象(即使 .Dispose 也不会这样做)。

如果系统承受很大压力,GC 可能不会经常运行。

简而言之:您正在“泄漏”数千个Texture2D。

加载它们的正确方法是在初始化事件之一中并将它们存储在类变量、字典、列表或任何结构中。基本上,只加载一次,然后重复使用它。

如果您必须按需加载纹理,只需加载一次并将其存储在列表/字典/类变量中,然后再次重用它。

编辑:您的“加载图像、释放、加载、释放”方法在.net中不起作用,只是因为您无法显式释放内存。您可以调用 GC.Collect,但是 a) 也不能保证它,并且 b) GC.Collect 非常慢。

是否有特定原因导致您必须每秒重新加载图像 60 次?如果您只需要每秒左右重新加载一次,那么您可以使用 elapsedGameTime 来测量自上次 .Update 以来过期的时间,如果超过您的阈值,则重新加载图像。(您需要一个像“LastUpdated”这样的类变量,您可以与它进行比较,并在重新加载图像时更新它)