Подтвердить что ты не робот

Правильный способ выпуска COM-объектов?

Иногда, когда я заканчиваю приложение, и он пытается освободить некоторые COM-объекты, я получаю предупреждение в отладчике:

RaceOnRCWCleanUp был обнаружен

Если я пишу класс, который использует COM-объекты, мне нужно реализовать IDisposable и вызвать Marshal.FinalReleaseComObject на них в IDisposable.Dispose, чтобы их правильно отпустить?

Если Dispose не вызывается вручную, тогда мне все равно нужно освободить их в финализаторе или будет автоматически выпущена GC? Теперь я вызываю Dispose(false) в финализаторе, но мне интересно, правильно ли это.

В COM-объекте, который я использую, также есть обработчик событий, который прослушивает класс. По-видимому, событие возникает в другом потоке, так как правильно его обрабатывать, если он уволен при утилизации класса?

4b9b3361

Ответ 1

Основываясь на моем опыте использования разных COM-объектов (in-process или out-of-process), я бы предложил один Marshal.ReleaseComObject для одного пересечения границ COM/.NET(если для экземпляра вы ссылаетесь на COM-объект, чтобы получить другой ссылкой на COM).

У меня много проблем, потому что я решил отложить очистку COM-взаимодействия до GC. Также обратите внимание, что я никогда не использую Marshal.FinalReleaseComObject - некоторые COM-объекты являются одноточечными и не работают с такими объектами.

Выполнение чего-либо в управляемых объектах внутри финализатора (или Dispose (false) из известной реализации IDisposable) запрещено. Вы не должны полагаться на какую-либо ссылку на объект .NET в финализаторе. Вы можете освободить IntPtr, но не COM-объект, поскольку он уже может быть очищен.

Ответ 2

Здесь есть статья о том, что: 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(). Оба требуются для грязного кода Interop.

Dispose НЕ автоматически вызывается GC. Когда объект располагается, деструктор вызывается (в другом потоке). Обычно вы можете освободить любую неуправляемую память или ссылки com.

Деструкторы: http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

... когда ваше приложение инкапсулирует неуправляемые ресурсы, такие как окна, файлы и сетевые подключения, вы должны использовать деструкторы для освобождения этих ресурсов. Когда объект имеет право на уничтожение, сборщик мусора запускает метод Finalize объекта.

Ответ 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 и StackOverflow.

Что, в конце концов, убедило меня в более пристальном взгляде и выяснить правильный совет, был этот пост https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/ вместе с поиском проблемы со ссылками поддерживался под отладчиком в ответ на какой-то ответ StackOverflow.