Правильный способ освобождения COM-объектов?
Иногда, когда я заканчиваю приложение и оно пытается освободить некоторые COM-объекты, я получаю предупреждение в отладчике:
RaceOnRCWCleanUp
был обнаружен
Если я напишу класс, который использует объекты COM, мне нужно реализовать IDisposable
и позвонить Marshal.FinalReleaseComObject
на них в IDisposable.Dispose
правильно отпустить их?
Если Dispose
тогда не вызывается вручную, мне все еще нужно выпустить их в финализаторе или GC выпустит их автоматически? Теперь я звоню Dispose(false)
в финализаторе, но мне интересно, правильно ли это.
У COM-объекта, который я использую, также есть обработчик событий, который слушает класс. Очевидно, что событие возникает в другом потоке, так как мне правильно его обработать, если оно запускается при удалении класса?
3 ответа
Во-первых, вам никогда не придется звонить Marshal.ReleaseComObject(...)
или же Marshal.FinalReleaseComObject(...)
при работе в Excel Это противоречивый шаблон, но любая информация об этом, в том числе от Microsoft, которая указывает на то, что вы должны вручную выпускать ссылки COM из.NET, неверна. Дело в том, что среда выполнения.NET и сборщик мусора правильно отслеживают и очищают ссылки COM.
Во-вторых, если вы хотите убедиться, что ссылки COM на внепроцессный объект COM очищены по завершении вашего процесса (чтобы процесс Excel закрылся), вам нужно убедиться, что сборщик мусора работает. Вы делаете это правильно с помощью звонков GC.Collect()
а также GC.WaitForPendingFinalizers()
, Вызов дважды безопасен, завершение гарантирует, что циклы также будут очищены.
В-третьих, при работе под отладчиком локальные ссылки будут искусственно поддерживаться до конца метода (чтобы проверка локальных переменных работала). Так что GC.Collect()
звонки не эффективны для очистки объекта, как rng.Cells
из того же метода. Вы должны разделить код, выполняющий COM-взаимодействие из очистки GC, на отдельные методы.
Общая схема будет такой:
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
Существует много ложной информации и путаницы по этому вопросу, в том числе много сообщений в MSDN и Stackru.
Что окончательно убедило меня поближе взглянуть и найти правильный совет, так это сообщение https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/ вместе с поиском проблема со ссылками, оставшимися живыми под отладчиком на некоторый ответ Stackru.
Основываясь на моем опыте использования различных COM-объектов (в процессе или вне процесса), я бы предложил один Marshal.ReleaseComObject
за одно пересечение границы COM/ .NET (если для экземпляра вы ссылаетесь на COM-объект для получения другой COM-ссылки).
Я столкнулся со многими проблемами только потому, что решил отложить очистку взаимодействия COM до GC. Также обратите внимание, я никогда не использую Marshal.FinalReleaseComObject
- некоторые COM-объекты являются одиночными, и с такими объектами это не очень хорошо работает.
Делать что-либо в управляемых объектах внутри финализатора (или Dispose (false) от известного IDisposable
реализация) запрещена. Вы не должны полагаться на любую ссылку на объект.NET в финализаторе. Вы можете выпустить IntPtr
, но не COM-объект, так как он уже может быть очищен.
Здесь есть статья об этом: http://www.codeproject.com/Tips/235230/Proper-Way-of-Releasing-COM-Objects-in-NET
В двух словах:
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.
До тех пор, пока GC не появится естественным образом, ссылка на com не будет полностью освобождена. Вот почему так много людей должны принудительно уничтожать объекты с помощью FinalReleaseComObject() и GC.Collect(). Оба необходимы для грязного кода взаимодействия.
Утилизация НЕ вызывается автоматически GC. Когда объект удаляется, вызывается деструктор (в другом потоке). Обычно это место, где вы можете освободить любую неуправляемую память или ком-ссылки.
Деструкторы: http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx
... когда ваше приложение инкапсулирует неуправляемые ресурсы, такие как окна, файлы и сетевые подключения, вы должны использовать деструкторы для освобождения этих ресурсов. Когда объект подлежит уничтожению, сборщик мусора запускает метод Finalize объекта.