您是否有时必须使用COM对象显式调用gc.collect

时间:2012-01-19 20:50:31

标签: .net com garbage-collection

这是功能:

        Private Sub LoadEmail()

        Dim loSession As RDOSession = Nothing
        Dim loMessages As RDOItems = Nothing

        Try
            moNDRs = New List(Of NonDeliveryRecord)
            loSession = New Redemption.RDOSession
            loSession.LogonExchangeMailbox(MailAccountName, MailServerName)
            loMessages = loSession.GetDefaultFolder(rdoDefaultFolders.olFolderInbox).Items
            Dim Counter = 0
            For Each loMessage As RDOMail In loMessages
                Counter += 1
                moNDRs.Add(CreateNDRRecord(loMessage))
                Marshal.ReleaseComObject(loMessage)
                loMessage = Nothing
                If Counter Mod 100 = 0 Then GC.Collect()
            Next


        Finally
            If loSession IsNot Nothing Then
                loSession.Logoff()
                Marshal.FinalReleaseComObject(loSession)
                loSession = Nothing
            End If

            If loMessages IsNot Nothing Then
                Marshal.FinalReleaseComObject(loMessages)
                loMessages = Nothing
            End If

        End Try

    End Sub

上面使用的Message类是Redemption。如果你查看上面的函数,你会看到:

If Counter Mod 100 = 0 Then GC.Collect()

我必须采取哪些措施来解决我们遇到的问题。今天早上我一直在玩记忆探测器(蚂蚁和dottrace),看看我能不能找到任何东西,但到目前为止一切看起来都很好。我不低级别谁也不会知道windgb的来龙去脉。

我得到的错误是: IMAPISession :: OpenEntry:MAPI_E_TOO_BIG

中的错误

我总是得到错误的行在下面的代码中注释。我总是在~450次迭代后得到错误。

这是你必须使用gc.collect的几次之一是你处理COM对象的时候吗?

这是CreateNDR函数,其中包含错误发生的行:

        Public Function CreateNDRRecord(ByVal voMessage As RDOMail) As NonDeliveryRecord

        Dim loItem As RDOReportItem = Nothing
        Dim loMatches As MatchCollection = Nothing
        Dim loNonDeliveryCode As NonDeliveryRecord = New NonDeliveryRecord
        Dim lsMessage As String = String.Empty


        Try
            loNonDeliveryCode.IsBadMessage = False
            loNonDeliveryCode.MailMessageId = voMessage.EntryID

            'Debug.Print(voMessage.MessageClass.Equals("REPORT.IPM.Note.NDR").ToString())
            If voMessage.MessageClass.Equals("REPORT.IPM.Note.NDR") Then 'error always happens here
                loItem = CType(voMessage, RDOReportItem)
                If voMessage.Recipients.Count <> 0 Then
                    loNonDeliveryCode.EmailAddress = voMessage.Recipients(1).Name
                End If
                loNonDeliveryCode.IsUndeliverable = True
                lsMessage = loItem.ReportText

            ElseIf voMessage.Subject.Contains(mconSeparator) Then
                loNonDeliveryCode.EmailAddress = voMessage.Subject.Substring(voMessage.Subject.LastIndexOf(mconSeparator) + mconSeparator.Length)
                loNonDeliveryCode.ErrorCode = String.Empty
                loNonDeliveryCode.IsUndeliverable = True
                lsMessage = voMessage.Body
            End If

            If loNonDeliveryCode.IsUndeliverable Then

                loMatches = GetErrorType(lsMessage)

                If loMatches.Count > 0 Then
                    loNonDeliveryCode.ErrorCode = loMatches(loMatches.Count - 1).Value
                End If

                Dim loNDRId = GetErrorCode(loNonDeliveryCode.ErrorCode)

                If loNDRId.Count > 0 Then
                    loNonDeliveryCode.ErrorCodeId = CType(CType(loNDRId(0), DataRow).Item("NonDeliveryCodeId"), Integer)
                    loNonDeliveryCode.ErrorDescription = CType(CType(loNDRId(0), DataRow).Item("Description"), String)
                    loNonDeliveryCode.MarkAsInvalid = CType(CType(loNDRId(0), DataRow).Item("MarkAsInvalid"), Boolean)
                Else
                    If voMessage.MessageClass.Equals("REPORT.IPM.Note.NDR") Then
                        loNonDeliveryCode.ErrorCode = String.Empty
                        loNDRId = GetErrorCode(loNonDeliveryCode.ErrorCode)
                        loNonDeliveryCode.ErrorCodeId = CType(CType(loNDRId(0), DataRow).Item("NonDeliveryCodeId"), Integer)
                        loNonDeliveryCode.ErrorDescription = CType(CType(loNDRId(0), DataRow).Item("Description"), String)
                        loNonDeliveryCode.MarkAsInvalid = CType(CType(loNDRId(0), DataRow).Item("MarkAsInvalid"), Boolean)
                    Else
                        loNonDeliveryCode.ErrorCode = String.Empty
                        loNonDeliveryCode.ErrorCodeId = 1
                    End If
                End If

            End If


            Return loNonDeliveryCode

        Catch Ex As Exception
            loNonDeliveryCode.IsUndeliverable = False
            loNonDeliveryCode.IsBadMessage = True
            Return loNonDeliveryCode

        Finally
            If loItem IsNot Nothing Then
                Marshal.FinalReleaseComObject(loItem)
                loItem = Nothing
            End If

            If voMessage IsNot Nothing Then Marshal.ReleaseComObject(voMessage)

            If loMatches IsNot Nothing Then
                loMatches = Nothing
            End If

        End Try

2 个答案:

答案 0 :(得分:5)

有些情况下程序只是没有消耗足够的垃圾收集内存来调用终结器线程来清理资源。例如,麻烦制造者是Thread类。它消耗5个操作系统句柄,但没有Dispose()方法来提前释放它们。和你正在使用的COM coclass一样,CLR为它们创建的托管包装器有一个终结器但是没有实现IDisposable。用户程序无法有效或可靠地调用Dispose()的类的示例。

GC.Collect()是针对此类案例而制作的。同时调用GC.WaitForPendingFinalizers(),因为那是你真正想要发生的事情。

您对它的使用是正确且可辩护的。你必须调整它。

答案 1 :(得分:4)

您可能遇到的问题是托管堆无法理解实时COM对象的真实内存占用。

从.NET运行时的角度来看,您的COM对象具有自动创建的运行时可调用包装器(RCW)的占用空间,这是很小的。您可能已经实例化了一个容纳大量内存的COM对象,但该内存并不存在于.NET内存堆中,因此垃圾收集器不会感到任何清理它的压力。

强制垃圾收集器在这种情况下运行似乎是正确的做法。它将处理任何未引用的RCW,它们将在COM对象上删除COM引用计数,从而导致它被释放(假设当然没有其他COM引用该对象)。