使用弱引用和GC进行渲染

时间:2014-04-18 13:33:07

标签: c# garbage-collection weak-references

问题

我最近开始学习C#。我是通过制作游戏来做到这一点的(因为我在C++中非常熟悉这一点。)

要在后台缓冲区中绘制的对象在构造时通过发送带有对象的弱引用的事件进行“注册”。由于C++智能指针被引用计数,并且(因此)一旦最后一个引用超出范围就被销毁,这对我的目的很有效。但是,在C#中,情况肯定不是这样。

请考虑以下代码段:

foreach(WeakReference<DrawableObject> drawable in drawables.ToList())
{
    DrawableObject target;
    drawable.TryGetTarget(out target);
    if(target != null)
    {
        spriteBatch.Draw(target.Texture, target.Position, target.Rectangle, target.Colour);
    }
    else
    {
        drawables.Remove(drawable);
    }
}

C#中存在的主要问题(因为我确信很有可能是我尚未理解的其他问题)是target不能保证null之后对象的最后一个强引用超出了范围。


潜在解决方案

强制垃圾回收

我可以事先在适当的时间强制调用GC,但在对该想法进行一些研究后,我发现这很可能是一个坏主意和/或代码中其他问题的迹象。

使用Hide()方法

虽然这样的方法对于任何游戏都是必不可少的,但是必须明确地调用Hide()每个可绘制对象都是乏味的并且给出了更多的错误空间。此外,何时应该调用所有这些Hide()?我也不能依赖于及时收集的父对象,因此将它们放在Finalizer中并不能解决问题。

实施IDisposable

与上述类似,可选择using语句(我不知道在哪里放置,因为对象需要存活,直到父母超出范围)。


问题

我能想到的解决方案似乎都没有适当地解决这个问题。我当然错过了不以这种方式使用弱引用的想法,但是如果找不到可以在当前状态下使用游戏的适当解决方案,则必须考虑这样做。所有这些归结为一些相关的问题:

  • 这是否是强制垃圾回收的合适用例

  • 如果没有,我如何确定某个对象到期是否为 下次运行GC时收集了什么?

  • 有没有办法尽快调用对象的方法 对它的引用超出了范围(隐式或显式), 而不是等待GC出现?

我当然感谢任何可以完全避免这个问题的建议。

1 个答案:

答案 0 :(得分:2)

我将尝试回答这个问题。

你要找的是“我根据游戏规则死了”的逻辑。不是“那个对象已经死了,因为垃圾收集器说的那么”逻辑。

为此......你需要将你的想法转变为这样的事情:

class DrawableObject {
    public bool IsDead { get; set; }
    public int Health { get; set; }

    // ...
    public void GetShot(int amount) {
        Health -= amount;

        if (Health <= 0)
            IsDead = true;
    }
}

..然后:

foreach (var drawable in drawables) {

    DrawableObject target;
    drawable.TryGetTarget(out target);

    if(target == null || target.IsDead) {
        drawables.Remove(drawable);
    }
    else {
        spriteBatch.Draw(target.Texture, target.Position, target.Rectangle, target.Colour);
    }
}

如果null基于您的TryGetXXX逻辑..或者它已被您的游戏逻辑标记为已死...将其从可绘制对象列表中删除。让垃圾收集器在它之后随时清理它。

TLDR:将你的物品标记为死亡,并将你的逻辑基于你自己的“我死了”的旗帜。