现在的代码处于相当可怕的状态(我正在研究它),我正在处理的代码的当前版本是在Finalization修复程序(UNSTABLE!)分支中.该分支中有两个变更集,为了理解我的冗长问题,我们需要同时处理这两个变更集. (变更集:ee6a002df36f和a12e9f5ea9fe)
对于某些背景,此项目是用C编写的unmanaged library的C/C++LI包装器.我不是该项目的协调员,有几个我不同意的设计决策,因为我相信很多看过这些代码的人会,但我离题了.我们在C/C++LI dll中包含了大部分原始库层,但是在C#dll中展示了易于使用的API.这样做是因为项目的目的是将整个库转换为托管C#代码.
如果您能够获取要编译的代码,则可以使用this test code重现该问题.
问题
名为moved resource management code to finalizers,to show bug
的最新变更集显示了我遇到的原始问题.此代码中的每个类都使用相同的模式来释放非托管资源.这是一个例子(C/C++LI):
DBContext::~DBContext() { this->!DBContext(); GC::SuppressFinalize(this); } DBContext::!DBContext() { if(_pst.get() != nullptr) _pst.reset(); // _pst is a clr_scoped_ptr (managed type) // that wraps a shared_ptr<T>. }
此代码有两个好处.首先,当像这样的类在using语句中时,资源会立即正确释放.其次,如果用户忘记了处理,当GC最终决定完成该类时,将释放非托管资源.
这是这种方法的问题,我根本无法理解,有时,GC会决定最终确定一些用于枚举文件中数据的类.许多不同的PST文件都会发生这种情况,并且我已经能够确定它与被调用的Finalize方法有关,即使该类仍在使用中.
我可以在this file (download)1中始终如一地实现它.早期调用的终结器位于DBAccessor.cpp文件中的NodeIdCollection类中.如果您能够运行上面链接的代码(由于对boost库的依赖性,此项目很难设置),应用程序将因异常而失败,因为_nodes列表设置为null并且_db_由于终结器运行,指针被重置.
1)NodeIdCollection类中的枚举代码是否有任何明显的问题会导致GC在仍在使用时完成此类?
难看的解决方法
现在,我能够通过将每个终结器(!classname)中的所有资源管理代码移动到析构函数(~classname)来解决此问题.这已经解决了这个问题,虽然它没有解决我为什么要尽早完成课程的好奇心.
但是,这种方法存在问题,我承认这对设计来说更是个问题.由于代码中指针的大量使用,几乎每个类都处理自己的资源,并且需要处理每个类.这使得使用枚举非常难看(C#):
foreach (var msg in pst.Messages) { // If this using statement were removed,we would have // memory leaks using (msg) { // code here } }
作用于集合中的项目的using语句对我来说是错误的,但是,使用这种方法非常有必要防止任何内存泄漏.没有它,即使调用了pst类上的dispose方法,也永远不会调用dispose并且永远不会释放内存.
我有意尝试改变这种设计.这个代码第一次编写时的根本问题,除了我对C/C++LI几乎一无所知之外,我无法将本地类放在托管代码中.我觉得有可能使用范围指针,当类不再使用时会自动释放内存,但我不能确定这是否是一种有效的方法来解决这个问题,或者它是否可行.所以,我的第二个问题是:
2)以无痛方式处理托管类中非托管资源的最佳方法是什么?
详细说明,我可以用最近添加到代码中的clr_scoped_ptr包装器替换本机指针(clr_scoped_ptr.h来自this stackexchange问题).或者我是否需要将本机指针包装在scoped_ptr< T>之类的内容中?或者smart_ptr< T>?
感谢您阅读所有这些,我知道这很多.我希望我已经足够清楚,以便我可以从比我更有经验的人那里得到一些见解.这是一个很大的问题,我打算在它允许的时候添加赏金.希望有人可以提供帮助.
谢谢!
1此文件是免费提供的PST文件enron dataset的一部分
解决方法
如果有任何错误,请告诉我.
即使我的代码不完美,使用智能指针也是解决此问题的正确方法,即使在托管代码中也是如此.
您不需要(也不应该)在终结器中重置clr_scoped_ptr.每个clr_scoped_ptr本身都将由运行时完成.
使用智能指针时,您不需要编写自己的析构函数或终结器.编译器生成的析构函数将自动调用所有子对象上的析构函数,并且每个子对象终结器将在收集时运行.
仔细观察您的代码,NodeIdCollection确实存在错误. GetEnumerator()每次调用时都必须返回一个不同的枚举器对象,以便每个枚举都从序列的开头开始.您正在重复使用单个枚举器,这意味着在连续调用GetEnumerator()之间共享该位置.那很糟.
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。