Sometimes when I end the application and it tries to release some COM objects, I receive a warning in the debugger:
RaceOnRCWCleanUp
was detected
If I write a class which uses COM objects, do I need to implement IDisposable
and call Marshal.FinalReleaseComObject
on them in IDisposable.Dispose
to properly release them?
If Dispose
is not called manually then, do I still need to release them in the finalizer or will the GC release them automatically? Now I call Dispose(false)
in the finalizer but I wonder if this is correct.
The COM object I use also have an event handler which the class listens to. Apparently the event is raised on another thread, so how do I correctly handle it if it is fired when disposing the class?
Based on my experience using different COM objects (in-process or out-of-process) I would suggest one Marshal.ReleaseComObject
per one COM/ .NET boundary crossing (if for an instance you reference COM object in order to retrieve another COM reference).
I have run into many issues just because I decided to postpone COM interop cleanup to GC.
Also please notice, I never use Marshal.FinalReleaseComObject
- some COM objects are singletons and it doesn't work well with such objects.
Doing anything in managed objects inside finalizer (or Dispose(false) from the well-known IDisposable
implementation) is forbidden. You must not rely on any .NET object reference in the finalizer. You can release IntPtr
, but not COM object as it could already be clean up.
There's an article here on that: http://www.codeproject.com/Tips/235230/Proper-Way-of-Releasing-COM-Objects-in-NET
In a nutshell:
1) Declare & instantiate COM objects at the last moment possible.
2) ReleaseComObject(obj) for ALL objects, at the soonest moment possible.
3) Always ReleaseComObject in the opposite order of creation.
4) NEVER call GC.Collect() except when required for debugging.
Until GC naturally occurs, the com reference will not be fully released. This is why so many people need to force object destruction using FinalReleaseComObject() and GC.Collect(). Both are required for dirty Interop code.
Dispose is NOT automatically called by the GC. When an object is being disposed, the destructor is called (in a different thread). This is usually where you could release any unmanaged memory, or com references.
Destructors: http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx
... when your application encapsulates unmanaged resources such as windows, files, and network connections, you should use destructors to free those resources. When the object is eligible for destruction, the garbage collector runs the Finalize method of the object.
First - you never have to call Marshal.ReleaseComObject(...)
or Marshal.FinalReleaseComObject(...)
when doing Excel interop. It is a confusing anti-pattern, but any information about this, including from Microsoft, that indicates you have to manually release COM references from .NET is incorrect. The fact is that the .NET runtime and garbage collector correctly keep track of and clean up COM references.
Second, if you want to ensure that the COM references to an out-of-process COM object is cleaned up when your process ends (so that the Excel process will close), you need to ensure that the Garbage Collector runs. You do this correctly with calls to GC.Collect()
and GC.WaitForPendingFinalizers()
. Calling twice is safe, end ensures that cycles are definitely cleaned up too.
Third, when running under the debugger, local references will be artificially kept alive until the end of the method (so that local variable inspection works). So a GC.Collect()
calls are not effective for cleaning object like rng.Cells
from the same method. You should split the code doing the COM interop from the GC cleanup into separate methods.
The general pattern would be:
Sub WrapperThatCleansUp()
' NOTE: Don't call Excel objects in here...
' Debugger would keep alive until end, preventing GC cleanup
' Call a separate function that talks to Excel
DoTheWork()
' Now Let the GC clean up (twice, to clean up cycles too)
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
End Sub
Sub DoTheWork()
Dim app As New Microsoft.Office.Interop.Excel.Application
Dim book As Microsoft.Office.Interop.Excel.Workbook = app.Workbooks.Add()
Dim worksheet As Microsoft.Office.Interop.Excel.Worksheet = book.Worksheets("Sheet1")
app.Visible = True
For i As Integer = 1 To 10
worksheet.Cells.Range("A" & i).Value = "Hello"
Next
book.Save()
book.Close()
app.Quit()
' NOTE: No calls the Marshal.ReleaseComObject() are ever needed
End Sub
There is a lot of false information and confusion about this issue, including many posts on MSDN and on StackOverflow.
What finally convinced me to have a closer look and figure out the right advice was this post https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/ together with finding the issue with references kept alive under the debugger on some StackOverflow answer.