C#中的垃圾回收;在特定线程中处理对象的释放

3
为了学习C#本地互操作性,我正在开发一个OpenGL包装器。OpenGL API本身是一个绑定到特定线程的状态机。当包含本地资源的对象被垃圾回收时,终结器在GC线程中运行,并且不能直接释放资源。
我目前采用的解决方法是在上下文对象中建立一个列表,对象将其资源添加到其中,在绘制循环的安全点迭代并释放它们。
然而,这样做的问题是,如果GC在迭代该列表时进行了集合修改,则foreach会失败。 我不能只是在列表周围放置互斥体,因为GC在大多数实现中都是停止整个程序,如果绘制循环已经锁定了它,则永远无法完成迭代并再次解锁它。
通常MTBF是游戏玩耍时间的两个小时左右,但如果故意进行应力测试,每秒几千个对象,它只需要几秒钟就会发生。
在这里可能最好的方法是什么?

你是否在容器中使用dispose模式,在线程终止之前释放资源? - Preet Sangha
你尝试过禁用并发GC吗?不确定是否相关,但值得测试。 - Adam Houldsworth
1
我不禁想到,在这里释放资源的最佳选择应该是使用 using / Dispose() 而不是使用终结器。 - Marc Gravell
这里没有使用dispose模式;对象是通过资产管理器创建的,以防止相同的模型/纹理被加载到VRAM中数百次。如果一辆车加载了一个模型,然后在从场景中移除时将其处理掉,所有的车都会失去它们的模型。我怀疑禁用并发GC也不会有太大帮助;它仍然可以在foreach中进行收集吗?using/Dispose()模式通常很有用,但在这种情况下不适用;实体生成这些对象,在调用Draw()时使用它们,并保留它们直到被销毁。感谢您迄今为止提供的意见。 - jameswilddev
1
你能为模型数据使用某种引用计数吗?这样只有在调用Dispose时引用计数达到0时才释放未受管理的数据。 - Dirk
曾经考虑过这样做,但引用计数并不理想,如果可能的话,我想避免使用它。我猜我可以在每次请求资产时创建一个“票据”对象,并使用它来防止虚假的引用释放(如果列表中没有该票据,则抛出异常,否则删除它,如果列表为空,则释放资源)。 - jameswilddev
4个回答

1
然后你需要咬紧牙关,停止依赖垃圾回收器为你进行资源管理。你需要让资产管理器有一个明确的函数来删除它所分配的对象,而不是依赖于资产管理器的终结器函数。并且你需要在代码的特定位置调用该函数。仅仅因为你拥有垃圾回收器,并不意味着它是最好或唯一的解决方案。

1
很有趣,所有东西看起来都像是钉子! - jameswilddev

1

提问者写道/陈述:

OpenGL API本身是一个状态机,绑定到特定的线程

这是错误的

OpenGL上下文一次只能在一个线程中活动。这并不意味着上下文不能从不同的线程中使用。实际上,OpenGL上下文是一个互斥资源(提示:Mutex),在线程中使用它之前必须先绑定它({wgl,glX}MakeCurrent(DC, RC)),完成所需的操作后,您需要从当前线程解除绑定OpenGL上下文({wgl,glX}MakeCurrent(NULL, NULL))。


我不能在一个完全随机的时间点接管GC线程(可能已经停止了整个程序)的控制权,对吧?目前正在使用GLFW打开窗口,所以我无法访问需要提供的参数,不过这可能会在以后改变。感谢澄清。 - jameswilddev

0

不确定它如何适用于该问题。该问题似乎并未涉及非托管指针,而是线程绑定资源。 - Marc Gravell
这个问题是关于防止GC在特定执行范围内进行收集。这正是fixed语句的作用。您说得对,fixed语句确实需要使用非托管代码,但阻止GC进行收集本质上就是这样。 - jaywayco
请完整阅读链接,您将看到如何使用fixed语句来固定托管变量的示例。 - jaywayco
@jaywayco,这个问题不是关于固定内存地址的。它与指针由于压缩而改变其值无关 - 它与线程有关。 - Marc Gravell
1
再次感谢您的建议,但问题的核心是我需要一种方法将引用从GC线程传递到特定线程,这在某些平台上很困难,因为GC会在某些平台上停止整个程序,这意味着使用同步对象将会使程序挂起,因为迭代资源列表以进行处理永远无法完成。 - jameswilddev
感谢 @jameswilddev 的解释。当人们对一个答案提出建设性的批评时,事情往往会保持得更加友好。我为自己搞混了一切而道歉。 - jaywayco

0

听起来你真正的问题是你有一些带有终结器的对象暴露了它们的资源。如果你要直接使用资源,你应该创建并存储在没有终结器的对象中,并确保你行使必要的纪律以防止资源泄漏。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接