【问题标题】:Which Excel Interop objects to clean up myself and which to clean up by GC.Collect()哪些 Excel 互操作对象要自己清理,哪些要由 GC.Collect() 清理
【发布时间】:2009-12-03 22:27:45
【问题描述】:

问题:

我想问一个问题以回复Mike Rosenblum's answerthis question。问题是关于清理 Excel 互操作对象。建议的几种解决方案(例如,包装器,不使用多个点,终止 excel 进程),但我最喜欢 Mike Rosenblum's solution 解决这个问题(lengthy article about the topic)。

它基本上说的是,您不必太担心所有浮动的引用。你只保留一些主要的(如ApplicationClassWorkbookWorksheet)。您首先调用垃圾回收来清理所有浮动的对象,然后通过调用 Marshal.FinalReleaseComObject 显式清理您仍然拥有的主要引用(按重要性相反的顺序)。

现在我对此有两个问题。
第一:我如何确定我需要保留哪些对象的引用?在Mike Rosenblum's example 中,他只保留RangesWorksheetsWorkbooksApplicationClasses
第二:如果对象比较多,如何确定清理的顺序(即“重要性顺序”)?

提前致谢。


更新 1:

MattC 建议,对于订单,唯一重要的是应用程序最后发布。尽管在我的reference 中有以下句子:“您还应该按重要性相反的顺序释放命名引用:首先是范围对象,然后是工作表、工作簿,最后是您的 Excel 应用程序对象。”意味着有更多的排序。

nobugz 建议将所有内容设置为null,然后进行垃圾收集就足够了,但这似乎与Mike Rosenblum's article 的以下引用相矛盾:“那么,您会认为您可以设置所有变量 = Nothing,然后在最后调用GC.Collect(),这有时确实有效。但是,Microsoft Office 应用程序对释放对象的顺序很敏感,不幸的是,设置变量 = Nothing 然后调用@ 987654342@不保证对象的释放顺序。”

更新 2:

一些额外的信息: 在我自己的应用程序中,我用图表做了很多事情。我正在设置很多属性等。据我所知,有很多地方可以创建新的 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)
  • 是的,我不是 100% 确定 NoBugz 的意思。他的意思是命令重要还是不重要? (我有点不在讨论范围内,所以我可能忽略了前面所说的内容)。
  • 您应该以相反的重要性顺序释放对象的原因是,如果您在完成编辑属性和调用依赖子方法的方法之前释放父对象。在这种情况下,您可能会发现一个孤立的 com 对象,以及可能出现的奇怪症状,例如 ComException 或 InvalidCastExceptions。

标签: c# com garbage-collection excel-interop


【解决方案1】:

您应该可以轻松地在代码中找到可能的实时引用,它们将是您类中的字段。或者清理方法中的局部变量,这不太可能。链接中提供的列表只是您最有可能存储在字段中的对象。可能还有其他人,他们会让 Excel 和 Application 对象一样活跃。

我不认为我会推荐链接中提倡的手提钻方法,它只是隐藏了对包装死 COM 接口的 RCW 的潜在生命引用。最好的情况是,您可能会永久泄漏 RCW 对象,最坏的情况是,当它意外引用该对象时,它会使您的程序崩溃并出现异常。可以肯定的是,Bugz 不是您容易发现的。您所要做的就是将您的引用设置为 null,顺序无关紧要,然后收集。

【讨论】:

  • Mike Rosenblum 在此链接中声明:xtremevbtalk.com/showthread.php?t=160433 说:“那么,您可能会认为,您可以设置所有变量 = Nothing,然后在最后调用 GC.Collect(),这“由此我得出我必须做一些手动释放。您对这句话有何看法?
  • 什么意思?您是否不同意这种说法:“Microsoft Office 应用程序对对象的释放顺序很敏感”?
  • 他的意思是 COM 引用计数与“Office 应用程序在从 com 互操作中使用时对终止顺序很敏感”的声明无关。顺序可能很敏感的主要原因是几乎每个对象都有一个 .application 字段引用,并且许多较低级别的对象都有一个引用更高级别对象的父字段。一般来说,只需确保取消引用所有内容并使用 Marshal.FinalReleaseCOMObject(var)
【解决方案2】:

我遇到了类似的问题,这就是我的捕获,最后看起来像清理。希望对你有帮助。

        .......

        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 的理论来说是一个足够好的反例。
  • 按照 Mike 的建议进行两次收集、泵入终结器队列... gc.collect Gc.waitforpendingfinalizers gc.collect Gc.waitforpendingfinalizers
  • 在进一步阅读 Mike 的文章时,似乎只有 VSTO 解决方案才需要完成队列的双泵。但是,只要您不打算定期调用 GC.Collect waitforpendingfinalizers,那么性能命中就无关紧要了。
【解决方案3】:

我认为只要应用程序是最后一个,您就可以按任何顺序释放它们(只要它们不为空)。

然后执行 GC.Collect 以最终杀死 excel.exe 进程。

【讨论】:

  • 你确定吗?在我上面链接的答案中,Mike Rosenblum 指出:“您还应该按重要性相反的顺序释放命名引用:首先是范围对象,然后是工作表、工作簿,最后是 Excel 应用程序对象。”
  • 严格来说不,我错了。在处理子对象时,您可能仍然可以访问对象,即在摆脱范围后访问工作表。我想我假设你正在杀死所有东西,所以你不会访问任何引用。
  • 啊,所以你建议他说你必须以相反的重要性顺序处理,因为你可能仍然想在处理一个孩子之后使用父对象。我不认为这是故意的。我认为他的意思是您必须按此顺序处理它们,否则 Excel 可能会出现问题并且不会退出。
  • 好点。从文档中不清楚为什么这很重要(倒序),但 Mikes 的实用建议是正确的,如果您不遵循此建议,您可能会遇到 Excel 不会退出的情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多