我应该清理哪些Excel Interop对象,哪些需要通过GC.Collect()来清理?

3

问题:

我想就Mike Rosenblum的回答这个问题提出一个问题。这个问题是关于清理Excel互操作对象的。有几种解决方案被提出(例如:包装器、不使用超过一个点、杀死Excel进程),但我最喜欢Mike Rosenblum的解决方案关于这个主题的冗长文章)。

基本上,它的意思是你不需要过于担心所有的引用。只需保留一些主要的引用(如ApplicationClassWorkbookWorksheet)。首先调用垃圾回收来清理所有浮动的对象,然后通过调用Marshal.FinalReleaseComObject(按重要性的相反顺序)明确清理您仍然具有的主要引用。
现在我有两个问题。
第一个:我如何确定需要保留引用的对象?在Mike Rosenblum's example中,他只保留了RangesWorksheetsWorkbooksApplicationClasses
第二个:如果有更多的对象,我如何确定清理它们的顺序(即“重要性顺序”)?

更新1:

MattC建议,在排序方面,唯一重要的是应用程序最后发布。尽管在我的参考资料中,以下句子:“您还应按重要性的相反顺序发布已命名的引用:首先是范围对象,然后是工作表、工作簿,最后是Excel应用程序对象。”暗示了有更多的排序。

nobugz建议将所有内容设置为null,然后进行垃圾回收即可,但这似乎与Mike Rosenblum的文章中的以下引用相矛盾:“你会认为,你可以将所有变量设置为Nothing,然后在最后调用GC.Collect(),有时确实可以。然而,Microsoft Office应用程序对释放对象的顺序很敏感,不幸的是,将变量设置为Nothing,然后调用GC.Collect()并不能保证对象释放的顺序。”

一些额外的信息: 在我的应用程序中,我使用图表进行了很多操作。我设置了很多属性等等。据我所知,有许多地方我创建了新的COM对象。我尝试确保从不使用双点,并在我完成使用的所有对象上调用Marshal.FinalReleaseComObject。我没有使用包装器方法,因为它会引入很多嵌套。
当我的应用程序完成工作后,EXCEL.exe没有关闭。但是...当我告诉我的应用程序再次执行相同的工作时,它确实关闭了。当然,打开了一个新的EXCEL.exe,它没有关闭。现在我已经删除了所有的Marshal.FinalReleaseComObject调用,应用程序的工作完全相同。 EXCEL.exe会一直存在,直到我告诉我的应用程序重新执行该工作,但是这时会启动并保持一个新的EXCEL.exe

编辑:当我告诉我的应用程序执行其他非COM相关的工作时,过一段时间EXCEL.exe就消失了,但是现在没有新的EXCEL.exe出现。

不确定我可以从中得出什么结论...


Nobugz并不一定与Mike(来自上面的更新1)签订合同。基本模式是... object = null; 等等 Gc.waitforpendingfinalizers gc.collect Gc.waitforpendingfinalizers gc.collect Marshal.FinalReleaseCOMObject(object) - Anonymous Type
是的,我不确定NoBugz的意思是什么。他是指顺序很重要还是不重要?(我有点跟不上讨论了,可能忽略了之前说过的某些内容)。 - Matthijs Wessels
释放对象的顺序应该是按重要性的相反顺序进行,这样可以避免在编辑依赖子对象的属性和调用方法之前释放父对象。否则,您可能会发现一个孤立的组件对象,并出现一些奇怪的症状,如ComException或InvalidCastExceptions。 - Anonymous Type
3个回答

3
您的代码中应该很容易找到可能存在的实时引用,它们将是类中的字段。或者是清理方法中的局部变量,但这种情况不太可能发生。链接提供的列表只是您最有可能存储在字段中的对象。还可能有其他对象,它们会让Excel与Application对象一样保持活动状态。
我认为不建议像链接中所推荐的那样采取“大力出奇迹”的方法,因为这只会隐藏包装了已经失效的COM接口的RCW的潜在生命引用。最好的情况下,您可能会对RCW对象产生永久泄漏,最坏的情况下,在意外引用对象时会导致程序崩溃并抛出异常。这些都是难以发现的错误。您只需要将引用设置为空即可,顺序无关紧要,然后进行收集。

Mike Rosenblum在这个链接中说道:http://www.xtremevbtalk.com/showthread.php?t=160433:“你可能会认为,你可以将所有变量设置为Nothing,然后在最后调用GC.Collect(),有时确实可以起作用。然而,Microsoft Office应用程序对释放对象的顺序非常敏感,不幸的是,将变量设置为Nothing,然后调用GC.Collect()并不能保证对象的释放顺序。”从中我得出结论,我确实需要进行一些手动释放。您对这段引文有何看法? - Matthijs Wessels
你是什么意思?你不同意这个说法吗:“Microsoft Office 应用程序对释放对象的顺序很敏感”? - Matthijs Wessels
1
他的意思是COM引用计数与“从com interop使用Office应用程序时终止顺序敏感”这个语句无关。顺序可能敏感的主要原因是几乎每个对象都有一个.application字段引用,许多较低级别的对象都有一个父字段,该字段引用更高级别的对象。通常只需确保取消引用所有内容并使用Marshal.FinalReleaseCOMObject(var)。 - Anonymous Type

2

我曾经遇到过类似的问题,这是我的catch和finally部分用于清理的代码,希望对你有所帮助。

        .......

        oWB._SaveAs(strCurrentDir +
           strFile, XlFileFormat.xlWorkbookNormal, null, null, false, false,       XlSaveAsAccessMode.xlShared, false, false, null, null);
            sumsheet.Activate();
            oWB.Close(null, null, null);
            oXL.Workbooks.Close();
            oXL.Quit();
        }
        catch (Exception theException)
        {
            theException.ToString();
        }
        #region COM Object Cleanup
        finally
        {
            // Cleanup
            GC.Collect();
            GC.WaitForPendingFinalizers();

            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oRng);
            //System.Runtime.InteropServices.Marshal.FinalReleaseComObject(sumSheet);
            //System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oSheet);
            //oWB.Close(null, null, null);
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oWB);
            oXL.Quit();
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oXL);
        }
        #endregion

编辑

如果您注意到我已经注释掉了sumSheet + oSheet(它们是我的工作表),因为这是不必要的。这段代码对我来说很稳定,没有问题。我发现通过重新排列顺序,我会遇到错误。


在生成错误的另一个顺序中,您最后是否保留了oXL?因为这将是MattC理论的足够反例。 - Matthijs Wessels
1
按照Mike的建议,执行两次收集并将其推送到终结器队列... gc.collect Gc.waitforpendingfinalizers gc.collect Gc.waitforpendingfinalizers - Anonymous Type
在进一步阅读Mike的文章后,似乎只有VSTO解决方案才需要对finalization队列进行双重泵送。然而,只要您不打算经常调用GC.Collect和WaitForPendingFinalizers,性能损失就不应该成为问题。 - Anonymous Type

0

我认为只要应用程序是最后一个,你可以以任何顺序发布它们(只要它们不为空)。

然后执行GC.Collect来最终终止excel.exe进程。


你确定这个吗?在我之前链接的答案中,Mike Rosenblum说:“你还应该按重要性的相反顺序发布命名引用:首先是区域对象,然后是工作表,工作簿,最后是你的Excel应用程序对象。” - Matthijs Wessels
严格来说,不是的,我错了。在处理子对象时,仍然有可能访问到一个对象,例如,在清除范围后访问工作表。我猜想你会将所有内容都清除掉,所以不会再访问任何引用。 - MattC
啊,所以你的建议是他说你必须按重要性的相反顺序处理,因为你可能仍然希望在释放子对象之后使用父对象。 我不认为那是他的意思。我认为他的意思是你必须按照那个顺序处理它们,否则Excel会出问题并且无法退出。 - Matthijs Wessels
1
好的观点。从文档中不清楚为什么这很重要(反向顺序),但迈克的实用建议是正确的,如果不遵循这个建议,你可能会遇到Excel无法退出的情况。 - Anonymous Type

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