2013-03-31 10 views
9

Czasami, kiedy zakończyć aplikację i próbuje zwolnić niektóre obiekty COM, otrzymałem ostrzeżenie w debugerze:Właściwy sposób zwalniania obiektów COM?

RaceOnRCWCleanUp wykryto

Jeśli piszę klasy, która używa obiektów COM, czy muszę zaimplementować IDisposable i zadzwonić pod numer Marshal.FinalReleaseComObject w IDisposable.Dispose, aby je poprawnie zwolnić?

Jeśli Dispose nie jest wywoływana ręcznie, czy nadal muszę zwolnić je w finalizatorze, czy GC automatycznie je zwolni? Teraz I call Dispose(false) in the finalizer, ale zastanawiam się, czy to jest poprawne.

Obiekt COM, którego używam, ma również obsługę zdarzeń, której słucha klasa. Wygląda na to, że zdarzenie jest wywoływane w innym wątku, więc jak poprawnie go obsłużyć, jeśli zostanie zwolniony podczas usuwania klasy?

+2

To przypomnienie, że ręczne zarządzanie pamięcią nie jest świetnym pomysłem. Ta MDA ostrzega z powodu potencjalnych poważnych awarii w serwerze COM. Próba wyrzucenia czegokolwiek podczas wyłączania aplikacji jest bezcelowa, zostanie sfinalizowana o milisekundę później. GC już wie, jak to zrobić, unikaj pomocy. –

Odpowiedz

10

Na podstawie mojego doświadczenia z wykorzystaniem różnych obiektów COM (w trakcie procesu lub poza procesem) sugerowałbym jeden Marshal.ReleaseComObject na jedno przejście graniczne COM/.NET (jeśli dla instancji odwołujesz się do obiektu COM w celu pobrania innego Odniesienie COM).

Wystąpiłem w wielu kwestiach tylko dlatego, że zdecydowałem się odłożyć odświeżanie międzyoperacyjne COM do GC. Należy również zauważyć, że nigdy nie używam Marshal.FinalReleaseComObject - niektóre obiekty COM są singletonami i nie działa dobrze z takimi obiektami.

Robienie niczego w zarządzanych obiektach wewnątrz finalizera (lub usunięcie (fałsz) ze znanej implementacji IDisposable) jest zabronione. W finalizatorze nie można polegać na żadnych odwołaniach do obiektów .NET. Możesz zwolnić obiekt IntPtr, ale nie obiekt COM, ponieważ można go już wyczyścić.

+0

Co się stanie, jeśli moja klasa korzystająca z obiektu COM będzie istnieć do momentu zakończenia aplikacji? A jeśli "dokumentacja" obiektów COM "wymaga" wywoływania jakiejś metody "Deactivate"? (Nie jestem pewien, co robi, ale próbowałem go nie wywoływać i nic złego się nie dzieje.) –

6

Jest tu artykuł na ten temat: http://www.codeproject.com/Tips/235230/Proper-Way-of-Releasing-COM-Objects-in-NET

W skrócie:

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. 

Do GC naturalnie występuje odniesienie com nie zostanie w pełni zrealizowany. Właśnie dlatego tak wiele osób musi wymuszać zniszczenie obiektów za pomocą FinalReleaseComObject() i GC.Collect(). Oba są wymagane dla brudnego kodu Interop.

Dispose NIE jest automatycznie wywoływane przez GC. Gdy obiekt jest usuwany, wywoływany jest destruktor (w innym wątku). Zazwyczaj jest to miejsce, w którym można zwolnić dowolną niezarządzaną pamięć lub odwołania do com.

destruktory: http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

... kiedy aplikacja obudowuje niezarządzanych zasobów, takich jak Windows, plików i połączeń sieciowych, należy użyć destruktorów, aby uwolnić te zasoby. Gdy obiekt kwalifikuje się do zniszczenia, śmieciarz uruchamia metodę finalizacji obiektu.

+0

Dlaczego nie powinniśmy wywoływać GC.Collect() po GC.WaitForPendingFinalizers()? – modiX

+0

Nie jestem w 100% pewny siebie, ale jeśli przeczytasz artykuł, to powie: "GC.Collect zatrzyma cały proces .NET w systemie operacyjnym w celu zbierania śmieci. Za każdym wywołaniem GC.Collect możesz promować śmieci z Generacji 0 do Generacji 1, a następnie do Generacji 2. Gdy twoje śmieci dotrą do Generacji 2, musisz zakończyć proces odzyskiwania pamięci. " Być może dzieje się tak dlatego, że obiekty COM mogą zbyt długo utknąć w GC, wymuszając zbyt wiele wywołań GC. –

+0

Częściej połączenia z GC.Collect uniemożliwiają GC szybkie zwalnianie pamięci. Dzieje się tak dlatego, że obiekty długo żywe (tj. Generacja 2) są rzadziej sprawdzane przez GC; Problem polega na tym, że generowanie obiektu jest promowane za każdym razem, gdy GC je pomija (tzn. odniesienie do obiektu nadal istnieje), więc wywołanie obiektu GC.Collect jest przedwcześnie promowane, co oszukuje GC w przekonaniu, że są one długotrwałe jeśli nie są. – jonvw

5

pierwsze - ty nigdy musiał zadzwonić Marshal.ReleaseComObject(...) lub Marshal.FinalReleaseComObject(...) robiąc współdziałanie Excel.Jest to mylący wzorzec antydopingowy, ale wszelkie informacje na jego temat, w tym pochodzące od firmy Microsoft, wskazujące na ręczne zwolnienie odniesień COM z .NET są niepoprawne. Faktem jest, że środowisko uruchomieniowe .NET i garbage collector poprawnie śledzą i czyszczą odwołania COM.

Po drugie, jeśli chcesz mieć pewność, że odwołania COM do poza procesem obiektu COM zostaną wyczyszczone po zakończeniu procesu (tak, że proces programu Excel zostanie zamknięty), musisz upewnić się, że działa Garbage Collector . Robisz to poprawnie, dzwoniąc pod numer GC.Collect() i GC.WaitForPendingFinalizers(). Podwójne wywoływanie jest bezpieczne, koniec zapewnia, że ​​cykle są zdecydowanie czyszczone.

Po trzecie, podczas działania pod debuggerem, lokalne odwołania będą sztucznie utrzymywane przy życiu aż do zakończenia metody (tak, że sprawdza się zmienna lokalna). Tak więc wywołania GC.Collect() nie są skuteczne w czyszczeniu obiektów takich jak rng.Cells z tej samej metody. Powinieneś podzielić kod wykonując interop COM od oczyszczania GC na osobne metody.

Ogólny wzór będzie:

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 

Istnieje wiele fałszywych informacji i zamieszanie na temat tego problemu, w tym wiele stanowisk na MSDN i na StackOverflow.

Co w końcu przekonało mnie do bliższego przyjrzenia się i znalezienia właściwej porady, to post https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/ wraz z odnalezieniem problemu z referencjami utrzymywanymi przy życiu w ramach debuggera na temat niektórych odpowiedzi StackOverflow.

+0

dziękuję za komentarz, ale jak dokładnie powinienem użyć twojej sugestii - z tego co widzę "DotheWork" to Sub, gdzie robisz rzeczy z Excelem, a "WrapperThatCleansUp" powinno czyścić obiekty COM - ale od którego punktu zdecydujesz się zadzwonić to Sub? Jeśli umieścisz go w miejscu, w którym robisz rzeczy w Excelu (jak 2DoTheWork "Sub), to jesteś w pętli" – LuckyLuke82

+0

"WrapperThatCleansUp" robi dwie rzeczy: (1) Zadzwoń do Sub, która wykonuje wywołania COM, i (2) Wywołuje Garbage Collector do oczyszczenia, kod wyższego poziomu wywoła "WrapperThatCleansUp".Podobnie jak w tym przykładzie, powinieneś umieścić pracę COM w oddzielnej procedurze do tej, z której wywołujesz GC, aby zapewnić przejrzysty zakres zmiennych lokalnych. – Govert

+0

To jest absolutnie niesamowite - najlepsze rozwiązanie! Byłem ostatnio na jednym z kursów Microsoftu i to, czego uczą i polecają w tym zakresie, jest zupełnie inne i dłuższe w kodzie w jakikolwiek sposób. Zalecają: 1.) stworzenie klasy implementującej interfejs IDisposable i metody zapisu dla obiektu COM wewnątrz (do tworzenia dokumentów, zapisywania itp.). 2.) wywołaj tę klasę, kiedy tylko chcesz, używając Wyciągu - a to powinno zająć się utylizacją obiektu ... Ale twój kod jest najlepszy, jaki widziałem i najkrótszy, więc dziękuję bardzo! – LuckyLuke82