Korzystam z interfejsu Excela w C # ( ApplicationClass
) i umieściłem następujący kod w mojej klauzuli w końcu:
while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }
excelSheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Chociaż ten rodzaj działa, Excel.exe
proces jest nadal w tle, nawet po zamknięciu programu Excel. Wydawany jest dopiero po ręcznym zamknięciu mojej aplikacji.
Co robię źle lub czy istnieje alternatywa dla zapewnienia, że obiekty międzyoperacyjne są odpowiednio usuwane?
c#
excel
interop
com-interop
Hades
źródło
źródło
Odpowiedzi:
Program Excel nie jest zamykany, ponieważ aplikacja nadal przechowuje odwołania do obiektów COM.
Myślę, że wywołujesz przynajmniej jednego członka obiektu COM bez przypisywania go do zmiennej.
Dla mnie był to obiekt excelApp.Worksheets, którego bezpośrednio użyłem bez przypisywania go do zmiennej:
Nie wiedziałem, że wewnętrznie C # stworzył opakowanie dla obiektu COM arkusza roboczego, który nie został zwolniony przez mój kod (ponieważ nie byłem tego świadomy) i był przyczyną, dla której Excel nie został zwolniony.
Znalazłem rozwiązanie mojego problemu na tej stronie , która ma również niezłą zasadę używania obiektów COM w C #:
Tak więc przy tej wiedzy właściwy sposób na wykonanie powyższych czynności jest następujący:
AKTUALIZACJA MORTEMU POST:
Chcę, aby każdy czytelnik bardzo uważnie przeczytał tę odpowiedź Hansa Passanta, ponieważ wyjaśnia ona pułapkę, w którą wpadłem i wielu innych programistów. Kiedy pisałem tę odpowiedź wiele lat temu, nie wiedziałem o wpływie debugera na śmietnik i wyciągnąłem błędne wnioski. Zachowuję swoją odpowiedź bez zmian ze względu na historię, ale proszę przeczytać ten link i nie przejść do „dwóch kropek”: Zrozumienie wyrzucania elementów bezużytecznych w .NET i Oczyszczanie obiektów Excel Interop z IDisposable
źródło
Możesz faktycznie zwolnić obiekt aplikacji Excel w czysty sposób, ale musisz uważać.
Porada, aby zachować nazwane odniesienie dla absolutnie każdego obiektu COM, do którego masz dostęp, a następnie jawnie go zwolnić,
Marshal.FinalReleaseComObject()
jest teoretycznie poprawna, ale niestety bardzo trudna do zarządzania w praktyce. Jeśli ktoś poślizgnie się gdziekolwiek i użyje „dwóch kropek” lub iteruje komórki za pomocąfor each
pętli lub innego podobnego polecenia, wówczas będziesz mieć niepowiązane obiekty COM i ryzykujesz zawieszenie. W takim przypadku nie byłoby sposobu na znalezienie przyczyny w kodzie; będziesz musiał przejrzeć cały kod naocznie i, miejmy nadzieję, znaleźć przyczynę, zadanie, które może być prawie niemożliwe w przypadku dużego projektu.Dobrą wiadomością jest to, że tak naprawdę nie musisz utrzymywać nazwanego odwołania do zmiennej do każdego używanego obiektu COM. Zamiast tego należy wywołać,
GC.Collect()
a następnieGC.WaitForPendingFinalizers()
zwolnić wszystkie (zwykle drobne) obiekty, do których nie ma się odwołania, a następnie jawnie zwolnić obiekty, do których należy nazwane odwołanie do zmiennej.Powinieneś również zwolnić swoje nazwane odwołania w odwrotnej kolejności ważności: najpierw obiekty zakresu, potem arkusze, skoroszyty, a na końcu obiekt aplikacji Excel.
Na przykład, zakładając, że masz nazwaną zmienną obiektu Range, nazwaną zmienną
xlRng
arkuszaxlSheet
roboczego, nazwaną zmienną skoroszytuxlBook
i nazwaną zmienną aplikacji ExcelxlApp
, wówczas kod czyszczenia może wyglądać następująco:W większości przykładów kodu zobaczysz do czyszczenia obiektów COM z .NET,
GC.Collect()
aGC.WaitForPendingFinalizers()
wywołania i są wykonywane DWUKROTNIE jak w:Nie powinno to być jednak wymagane, chyba że używasz programu Visual Studio Tools for Office (VSTO), który korzysta z finalizatorów, które powodują awans całego wykresu obiektów w kolejce finalizacji. Takie obiekty nie zostaną zwolnione do następnego wyrzucania elementów bezużytecznych. Jeśli jednak nie używasz VSTO, możesz zadzwonić
GC.Collect()
iGC.WaitForPendingFinalizers()
tylko raz.Wiem, że bezpośrednie dzwonienie
GC.Collect()
jest nie-nie (a na pewno robienie tego dwukrotnie brzmi bardzo bolesnie), ale szczerze mówiąc, nie można tego obejść. Poprzez normalne operacje wygenerujesz ukryte obiekty, do których nie masz żadnego odniesienia, dlatego nie możesz ich uwolnić w żaden inny sposób niż wywołanieGC.Collect()
.To złożony temat, ale tak naprawdę to wszystko. Po ustaleniu tego szablonu dla procedury czyszczenia możesz normalnie kodować, bez potrzeby owijania itp .:-)
Mam tutaj poradnik na ten temat:
Automatyzacja programów biurowych za pomocą VB.Net / COM Interop
Jest napisany dla VB.NET, ale nie zniechęcaj się, zasady są dokładnie takie same jak przy użyciu C #.
źródło
Przedmowa: moja odpowiedź zawiera dwa rozwiązania, więc uważaj podczas czytania i niczego nie przegap.
Istnieją różne sposoby i porady dotyczące rozładowywania wystąpienia programu Excel, takie jak:
Zwolnienie KAŻDEGO obiektu com jawnie za pomocą Marshal.FinalReleaseComObject () (nie zapominając o niejawnie utworzonych obiektach com). Aby zwolnić każdy utworzony obiekt com, możesz użyć reguły 2 kropek wymienionych tutaj:
Jak prawidłowo wyczyścić obiekty programu Excel interop?
Wywoływanie GC.Collect () i GC.WaitForPendingFinalizers () w celu uwolnienia CLR nieużywanych obiektów com * (Właściwie to działa, zobacz szczegóły w moim drugim rozwiązaniu)
Sprawdzanie, czy aplikacja na serwer może wyświetlać okno komunikatu czekające na odpowiedź użytkownika (chociaż nie jestem pewien, czy może to uniemożliwić zamknięcie programu Excel, ale słyszałem o tym kilka razy)
Wysyłanie wiadomości WM_CLOSE do głównego okna programu Excel
Wykonywanie funkcji współpracującej z programem Excel w osobnej AppDomain. Niektóre osoby uważają, że wystąpienie programu Excel zostanie zamknięte, gdy aplikacja AppDomain zostanie zwolniona.
Zabijanie wszystkich instancji programu Excel, które zostały utworzone po uruchomieniu naszego kodu współpracy programu Excel.
ALE! Czasami wszystkie te opcje po prostu nie pomagają lub nie mogą być odpowiednie!
Na przykład wczoraj dowiedziałem się, że w jednej z moich funkcji (która działa z programem Excel) program Excel działa po zakończeniu funkcji. Próbowałem wszystkiego! Dokładnie sprawdziłem całą funkcję 10 razy i do wszystkiego dodałem Marshal.FinalReleaseComObject ()! Miałem także GC.Collect () i GC.WaitForPendingFinalizers (). Sprawdziłem ukryte skrzynki wiadomości. Próbowałem wysłać wiadomość WM_CLOSE do głównego okna programu Excel. Wykonałem swoją funkcję w oddzielnej domenie aplikacji i zwolniłem tę domenę. Nic nie pomogło! Opcja zamykania wszystkich instancji programu Excel jest nieodpowiednia, ponieważ jeśli użytkownik uruchomi inną instancję programu Excel ręcznie, podczas wykonywania mojej funkcji, która działa również z programem Excel, ta instancja zostanie również zamknięta przez moją funkcję. Założę się, że użytkownik nie będzie szczęśliwy! Więc, szczerze mówiąc, jest to kiepska opcja (bez obrazy).rozwiązanie : Zabij proces Excel przez hWnd jego głównego okna (jest to pierwsze rozwiązanie).
Oto prosty kod:
Jak widać, podałem dwie metody, zgodnie ze wzorem Try-Parse (myślę, że jest to właściwe tutaj): jedna metoda nie zgłasza wyjątku, jeśli Proces nie może zostać zabity (na przykład proces już nie istnieje) , a inna metoda zgłasza wyjątek, jeśli proces nie został zabity. Jedynym słabym miejscem w tym kodzie są uprawnienia bezpieczeństwa. Teoretycznie użytkownik może nie mieć uprawnień do zabicia procesu, ale w 99,99% wszystkich przypadków użytkownik ma takie uprawnienia. Przetestowałem to również na koncie gościa - działa idealnie.
Twój kod działający z programem Excel może wyglądać następująco:
Voila! Excel został zakończony! :)
Ok, wróćmy do drugiego rozwiązania, jak obiecałem na początku postu. Drugim rozwiązaniem jest wywołanie GC.Collect () i GC.WaitForPendingFinalizers (). Tak, faktycznie działają, ale musisz uważać tutaj!
Wiele osób twierdzi (i powiedziałem), że wywołanie GC.Collect () nie pomaga. Ale to nie pomoże, jeśli nadal istnieją odniesienia do obiektów COM! Jednym z najbardziej popularnych powodów, dla których GC.Collect () nie jest pomocny, jest uruchomienie projektu w trybie debugowania. W obiektach w trybie debugowania, do których tak naprawdę nie ma już odniesienia, nie będą wyrzucane śmieci do końca metody.
Jeśli więc wypróbowałeś GC.Collect () i GC.WaitForPendingFinalizers () i to nie pomogło, spróbuj wykonać następujące czynności:
1) Spróbuj uruchomić projekt w trybie Release i sprawdź, czy program Excel został poprawnie zamknięty
2) Zawiń metodę pracy z programem Excel osobną metodą. Zamiast czegoś takiego:
ty piszesz:
Teraz program Excel zostanie zamknięty =)
źródło
AKTUALIZACJA : Dodano kod C # i link do zadań systemu Windows
Spędziłem trochę czasu próbując rozwiązać ten problem, a wtedy XtremeVBTalk był najbardziej aktywny i responsywny. Oto link do mojego oryginalnego postu, „ Zamykanie procesu Excel Interop”, nawet jeśli aplikacja ulegnie awarii . Poniżej znajduje się streszczenie posta, a kod skopiowany do tego postu.
Application.Quit()
iProcess.Kill()
działa w przeważającej części, ale kończy się niepowodzeniem, jeśli aplikacja ulegnie awarii. Tzn. Jeśli aplikacja ulegnie awarii, proces programu Excel nadal będzie działał luźno.Uważam, że jest to czyste rozwiązanie, ponieważ system operacyjny wykonuje prawdziwą pracę w zakresie czyszczenia. Wszystko, co musisz zrobić, to zarejestrować proces Excel.
Kod pracy systemu Windows
Zawija wywołania API Win32, aby zarejestrować procesy Interop.
Uwaga na temat kodu konstruktora
info.LimitFlags = 0x2000;
wywoływana jest funkcja .0x2000
jestJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
wartością wyliczania, a ta wartość jest zdefiniowana przez MSDN jako:Dodatkowe wywołanie API Win32, aby uzyskać identyfikator procesu (PID)
Korzystanie z kodu
źródło
To zadziałało dla projektu, nad którym pracowałem:
Dowiedzieliśmy się, że po zakończeniu korzystania z każdego odwołania do obiektu COM programu Excel należy wyzerować. Obejmowało to komórki, arkusze i wszystko.
źródło
Wszystko, co znajduje się w przestrzeni nazw Excela, musi zostać zwolnione. Kropka
Nie możesz robić:
Musisz to robić
następnie następuje zwolnienie obiektów.
źródło
Po pierwsze - nigdy nie musisz dzwonić
Marshal.ReleaseComObject(...)
aniMarshal.FinalReleaseComObject(...)
wykonywać operacji w programie Excel. Jest to mylący anty-wzorzec, ale wszelkie informacje na ten temat, w tym od Microsoft, wskazujące, że musisz ręcznie zwolnić odwołania COM z .NET, są nieprawidłowe. Faktem jest, że środowisko uruchomieniowe .NET i moduł czyszczenia pamięci poprawnie śledzą i usuwają odwołania COM. W przypadku kodu oznacza to, że możesz usunąć całą pętlę `while (...) u góry.Po drugie, jeśli chcesz się upewnić, że odwołania COM do obiektu COM poza procesem zostaną wyczyszczone po zakończeniu procesu (aby proces Excel się zamknął), musisz upewnić się, że moduł czyszczenia pamięci działa. Robisz to poprawnie z połączeniami do
GC.Collect()
iGC.WaitForPendingFinalizers()
. Dwukrotne wywołanie tego jest bezpieczne i zapewnia, że cykle są zdecydowanie wyczyszczone (choć nie jestem pewien, czy jest to potrzebne i doceniłbym przykład, który to pokazuje).Po trzecie, podczas działania w debuggerze lokalne odniesienia będą sztucznie utrzymywane przy życiu aż do końca metody (tak, aby lokalna kontrola zmiennych działała). Tak więc
GC.Collect()
wywołania nie są skuteczne w czyszczeniu obiektów jakrng.Cells
przy użyciu tej samej metody. Powinieneś podzielić kod wykonując interakcję COM z czyszczenia GC na osobne metody. (To było dla mnie kluczowe odkrycie, z jednej części odpowiedzi opublikowanej tutaj przez @nightcoder.)Ogólny wzór byłby zatem następujący:
Istnieje wiele fałszywych informacji i zamieszania na ten temat, w tym wiele postów na MSDN i na Stack Overflow (a szczególnie na to pytanie!).
To, co ostatecznie przekonało mnie do dokładniejszego przyjrzenia się i znalezienia właściwej porady, to post na blogu Marshal.ReleaseComObject uznany za niebezpieczny wraz ze znalezieniem problemu z referencjami utrzymywanymi przy debuggerze, który mylił moje wcześniejsze testy.
źródło
I znalazł użyteczne ogólny szablon, który może pomóc wdrożenie prawidłowego wzorca utylizacji dla obiektów COM, tę potrzebę Marshal.ReleaseComObject nazwie kiedy wychodzi z zakresu:
Stosowanie:
Szablon:
Odniesienie:
http://www.deez.info/sengelha/2005/02/11/useful-idisposable-class-3-autoreleasecomobject/
źródło
Nie mogę uwierzyć, że ten problem nawiedza świat od 5 lat ... Jeśli utworzyłeś aplikację, musisz ją najpierw zamknąć przed usunięciem linku.
podczas zamykania
Gdy nowa aplikacja Excela, otwiera program Excel w tle. Musisz wydać polecenie, aby ten program Excel zakończył działanie przed zwolnieniem łącza, ponieważ ten program Excel nie jest częścią twojej bezpośredniej kontroli. Dlatego pozostanie otwarty, jeśli link zostanie zwolniony!
Dobre programowanie dla wszystkich ~~
źródło
Zwykli programiści, żadne z twoich rozwiązań nie działało dla mnie, więc decyduję się na wdrożenie nowej sztuczki .
Najpierw określmy „Jaki jest nasz cel?” => „Nie widać obiektu Excela po naszej pracy w menedżerze zadań”
Ok. Nie rzucaj wyzwania i zacznij go niszczyć, ale rozważ, aby nie zniszczyć innych wystąpień programu Excel, które działają równolegle.
Pobierz listę bieżących procesorów i pobierz PID procesów EXCEL, a następnie, gdy twoje zadanie zostanie wykonane, mamy nowego gościa na liście procesów z unikalnym PID, znajdź i zniszcz tylko ten.
<pamiętaj, że każdy nowy proces programu Excel podczas pracy w programie Excel zostanie wykryty jako nowy i zniszczony> <Lepszym rozwiązaniem jest przechwycenie PID nowo utworzonego obiektu programu Excel i po prostu jego zniszczenie>
To rozwiązuje mój problem, mam nadzieję, że również twój.
źródło
To z pewnością wydaje się zbyt skomplikowane. Z mojego doświadczenia wynika, że prawidłowe zamknięcie programu Excel to tylko trzy kluczowe rzeczy:
1: upewnij się, że nie ma żadnych odwołań do utworzonej aplikacji Excel (powinieneś mieć tylko jedną; ustaw ją na
null
)2: połączenie
GC.Collect()
3: Excel musi zostać zamknięty przez użytkownika ręcznie zamykającego program lub przez wywołanie
Quit
obiektu Excel. (Należy pamiętać, żeQuit
będzie działać tak, jakby użytkownik próbował zamknąć program i wyświetli okno dialogowe potwierdzenia, jeśli wystąpią niezapisane zmiany, nawet jeśli program Excel nie jest widoczny. Użytkownik może nacisnąć przycisk Anuluj, a następnie program Excel nie zostanie zamknięty. )1 musi zdarzyć się przed 2, ale 3 mogą się zdarzyć w dowolnym momencie.
Jednym ze sposobów realizacji tego jest zawinięcie obiektu Excel interop własną klasą, utworzenie instancji interop w konstruktorze i implementacja IDisposable z Dispose wyglądającym jak
To wyczyści program Excel ze strony Twojego programu. Po zamknięciu programu Excel (ręcznie przez użytkownika lub przez telefon
Quit
) proces zniknie. Jeśli program został już zamknięty, proces zniknie podczasGC.Collect()
połączenia.(Nie jestem pewien, jak ważne to jest, ale możesz chcieć
GC.WaitForPendingFinalizers()
połączenia po nim,GC.Collect()
ale nie jest to absolutnie konieczne, aby pozbyć się procesu Excel).To działało dla mnie bez problemu od lat. Pamiętaj jednak, że podczas gdy to działa, w rzeczywistości musisz zamknąć się z gracją, aby zadziałało. Nadal będziesz gromadzić procesy excel.exe, jeśli przerwiesz program przed wyczyszczeniem programu Excel (zwykle poprzez naciśnięcie „stop” podczas debugowania programu).
źródło
Marshal
.Aby dodać do powodów, dla których program Excel nie zamyka się, nawet gdy tworzysz bezpośrednie odwołania do każdego obiektu podczas odczytu, tworzenie, jest pętlą „For”.
źródło
Tradycyjnie stosuję się do rad zawartych w odpowiedzi VVS . Jednak starając się aktualizować tę odpowiedź zgodnie z najnowszymi opcjami, myślę, że wszystkie moje przyszłe projekty będą korzystać z biblioteki „NetOffice”.
NetOffice jest kompletnym zamiennikiem Office PIA i jest całkowicie niezależny od wersji. Jest to kolekcja opakowań zarządzanych COM, które mogą obsługiwać czyszczenie, które często powoduje takie problemy podczas pracy z pakietem Microsoft Office w .NET.
Niektóre kluczowe funkcje to:
Nie jestem w żaden sposób związany z projektem; Naprawdę doceniam wyraźne zmniejszenie bólu głowy.
źródło
Przyjęta tutaj odpowiedź jest poprawna, ale należy również pamiętać, że należy unikać nie tylko odniesień do „dwóch kropek”, ale także obiektów, które są pobierane przez indeks. Nie musisz też czekać, aż program skończy wyczyścić te obiekty, najlepiej jest utworzyć funkcje, które wyczyszczą je, gdy tylko skończysz, jeśli to możliwe. Oto utworzona przeze mnie funkcja, która przypisuje niektóre właściwości obiektu Style o nazwie
xlStyleHeader
:Zauważ, że musiałem ustawić
xlBorders[Excel.XlBordersIndex.xlEdgeBottom]
zmienną, aby to wyczyścić (Nie z powodu dwóch kropek, które odnoszą się do wyliczenia, które nie muszą być zwolnione, ale dlatego, że obiekt, o którym mówię, jest w rzeczywistości obiektem Border który musi zostać wydany).Tego rodzaju rzeczy nie są tak naprawdę konieczne w standardowych aplikacjach, które wykonują świetną robotę po sobie, ale w aplikacjach ASP.NET, jeśli przegapisz choć jedną z nich, bez względu na to, jak często wywołujesz moduł wyrzucania elementów bezużytecznych, Excel nadal działa na twoim serwerze.
Wymaga to dużej dbałości o szczegóły i wykonywania wielu testów podczas monitorowania Menedżera zadań podczas pisania tego kodu, ale pozwala to uniknąć kłopotów z desperackim przeszukiwaniem stron kodu w celu znalezienia jednego pominiętego wystąpienia. Jest to szczególnie ważne podczas pracy w pętlach, w których należy zwolnić KAŻDĄ INSTANCJĘ obiektu, mimo że używa tej samej nazwy zmiennej za każdym razem, gdy pętla jest używana.
źródło
Po spróbowaniu
GC.Collect()
iGC.WaitForPendingFinalizers()
dwukrotnie na końcuostatecznym rozwiązaniem, które działa dla mnie, jest przeniesienie jednego zestawu
które dodaliśmy na końcu funkcji do opakowania, w następujący sposób:
źródło
Śledziłem dokładnie to ... Ale wciąż napotykałem problemy na 1 z 1000 razy. Kto wie dlaczego? Czas wydobyć młotek ...
Zaraz po utworzeniu instancji klasy aplikacji Excel otrzymuję właśnie utworzony proces Excel.
Potem, kiedy wykonam wszystkie powyższe operacje czyszczenia COM, upewniam się, że proces nie działa. Jeśli nadal działa, zabij go!
źródło
¸ ° º¤ø „¸ Shoot Excel proc i żuć gumę balonową ¸„ ø¤º ° ¨
źródło
Musisz pamiętać, że program Excel jest bardzo wrażliwy na kulturę, w której prowadzisz.
Może się okazać, że musisz ustawić kulturę na EN-US przed wywołaniem funkcji Excela. Nie dotyczy to wszystkich funkcji - ale niektórych z nich.
Dotyczy to nawet jeśli korzystasz z VSTO.
Aby uzyskać szczegółowe informacje: http://support.microsoft.com/default.aspx?scid=kb;en-us;Q320369
źródło
„Nigdy nie używaj dwóch kropek z obiektami COM” to wielka ogólna zasada, aby uniknąć wycieku referencji COM, ale Excel PIA może prowadzić do wycieku na więcej sposobów niż pozornie na pierwszy rzut oka.
Jednym z tych sposobów jest zasubskrybowanie dowolnego zdarzenia ujawnionego przez dowolny obiekt COM modelu obiektowego Excel.
Na przykład subskrybowanie zdarzenia WorkbookOpen klasy aplikacji.
Trochę teorii na temat zdarzeń COM
Klasy COM ujawniają grupę zdarzeń poprzez interfejsy zwrotne. Aby subskrybować zdarzenia, kod klienta może po prostu zarejestrować obiekt implementujący interfejs zwrotny, a klasa COM wywoła swoje metody w odpowiedzi na określone zdarzenia. Ponieważ interfejs oddzwaniania jest interfejsem COM, obowiązkiem obiektu implementującego jest zmniejszenie liczby referencji dowolnego otrzymanego obiektu COM (jako parametru) dla dowolnej procedury obsługi zdarzeń.
Jak Excel PIA eksponuje zdarzenia COM
Excel PIA przedstawia zdarzenia COM klasy aplikacji Excel jako konwencjonalne zdarzenia .NET. Ilekroć subskrybuje kod klienta do zdarzenia .NET (nacisk na „a”), PIA tworzy się instancji klasy implementującej interfejs oddzwonienia i rejestry to z Excela.
W związku z tym wiele obiektów oddzwaniania jest rejestrowanych w programie Excel w odpowiedzi na różne żądania subskrypcji z kodu .NET. Jeden obiekt oddzwaniania na subskrypcję zdarzenia.
Interfejs oddzwaniania do obsługi zdarzeń oznacza, że PIA musi subskrybować wszystkie zdarzenia interfejsu dla każdego żądania subskrypcji zdarzeń .NET. Nie można wybrać. Po otrzymaniu wywołania zwrotnego zdarzenia obiekt zwrotny sprawdza, czy powiązana procedura obsługi zdarzeń .NET jest zainteresowana bieżącym zdarzeniem, czy nie, a następnie albo wywołuje procedurę obsługi, albo dyskretnie ignoruje oddzwonienie.
Wpływ na liczbę odwołań do instancji COM
Wszystkie te obiekty oddzwaniania nie zmniejszają liczby referencyjnej żadnego z otrzymywanych obiektów COM (jako parametrów) dla żadnej z metod oddzwaniania (nawet tych, które są dyskretnie ignorowane). Opierają się wyłącznie na śmieciarzu CLR, aby uwolnić obiekty COM.
Ponieważ przebieg GC jest niedeterministyczny, może to prowadzić do wstrzymania procesu Excela na dłużej niż jest to pożądane i stworzyć wrażenie „wycieku pamięci”.
Rozwiązanie
Jedynym rozwiązaniem na razie jest uniknięcie dostawcy zdarzeń PIA dla klasy COM i napisanie własnego dostawcy zdarzeń, który deterministycznie uwalnia obiekty COM.
W przypadku klasy aplikacji można to zrobić, implementując interfejs AppEvents, a następnie rejestrując implementację w programie Excel za pomocą interfejsu IConnectionPointContainer . Klasa aplikacji (i w tym przypadku wszystkie obiekty COM eksponujące zdarzenia za pomocą mechanizmu zwrotnego) implementuje interfejs IConnectionPointContainer.
źródło
Jak zauważyli inni, musisz utworzyć jawne odwołanie dla każdego używanego obiektu Excel i wywołać Marshal.ReleaseComObject w tym odwołaniu, jak opisano w tym artykule z bazy wiedzy . Musisz także użyć try / wreszcie, aby upewnić się, że ReleaseComObject jest zawsze wywoływany, nawet gdy zostanie zgłoszony wyjątek. Tj. Zamiast:
musisz zrobić coś takiego:
Musisz także wywołać aplikację. Wyjdź przed zwolnieniem obiektu aplikacji, jeśli chcesz zamknąć program Excel.
Jak widać, szybko staje się to wyjątkowo nieporęczne, gdy tylko spróbujesz zrobić coś nawet umiarkowanie złożonego. Z powodzeniem opracowałem aplikacje .NET z prostą klasą opakowania, która otacza kilka prostych manipulacji modelem obiektowym Excel (otwórz skoroszyt, napisz do zakresu, zapisz / zamknij skoroszyt itp.). Klasa otoki implementuje IDisposable, dokładnie implementuje Marshal.ReleaseComObject na każdym używanym obiekcie i nie ujawnia publicznie żadnych obiektów Excela pozostałej części aplikacji.
Ale to podejście nie daje się dobrze skalować w przypadku bardziej złożonych wymagań.
Jest to duży niedobór .NET COM Interop. W przypadku bardziej skomplikowanych scenariuszy poważnie zastanowiłbym się nad napisaniem biblioteki DLL ActiveX w VB6 lub innym niezarządzanym języku, do którego można delegować całą interakcję z obiektami COM poza procesem, takimi jak Office. Następnie możesz odwołać się do tej biblioteki ActiveX DLL z aplikacji .NET, a rzeczy będą znacznie łatwiejsze, ponieważ będziesz musiał tylko opublikować to jedno odwołanie.
źródło
Gdy wszystkie powyższe rzeczy nie działają, spróbuj dać Excelowi trochę czasu na zamknięcie swoich arkuszy:
źródło
Pamiętaj, aby zwolnić wszystkie obiekty związane z programem Excel!
Spędziłem kilka godzin, próbując na kilka sposobów. Wszystkie są świetnymi pomysłami, ale w końcu znalazłem swój błąd: jeśli nie uwolnisz wszystkich obiektów, żadna z powyższych metod nie pomoże ci w moim przypadku. Upewnij się, że wypuściłeś wszystkie obiekty, w tym pierwszy!
Opcje są tutaj razem .
źródło
Świetny artykuł na temat zwalniania obiektów COM to 2.5 Zwalnianie obiektów COM (MSDN).
Metodą, którą zalecałbym, jest zerowanie odwołań do programu Excel.Interop, jeśli są to zmienne nielokalne, a następnie wywołanie
GC.Collect()
iGC.WaitForPendingFinalizers()
dwukrotne. Zmienne lokalne Interop będą obsługiwane automatycznie.Eliminuje to potrzebę zachowania nazwanego odwołania dla każdego obiektu COM.
Oto przykład zaczerpnięty z artykułu:
Te słowa pochodzą bezpośrednio z artykułu:
źródło
Reguła dwóch kropek nie działała dla mnie. W moim przypadku stworzyłem metodę czyszczenia moich zasobów w następujący sposób:
źródło
Moje rozwiązanie
źródło
Należy bardzo ostrożnie korzystać z aplikacji do współpracy Word / Excel. Po wypróbowaniu wszystkich rozwiązań nadal wiele procesów „WinWord” pozostawało otwartych na serwerze (z ponad 2000 użytkowników).
Po wielu godzinach pracy nad problemem zdałem sobie sprawę, że jeśli otworzę więcej niż kilka dokumentów
Word.ApplicationClass.Document.Open()
jednocześnie z różnymi wątkami, proces roboczy IIS (w3wp.exe) ulegnie awarii, pozostawiając otwarte wszystkie procesy WinWord!Sądzę więc, że nie ma absolutnego rozwiązania tego problemu, ale przejście na inne metody, takie jak tworzenie pakietu Office Open XML .
źródło
Przyjęta odpowiedź nie działała dla mnie. Poniższy kod w destruktorze wykonał zadanie.
źródło
Obecnie pracuję nad automatyzacją pakietu Office i natknąłem się na rozwiązanie, które działa za każdym razem dla mnie. Jest to proste i nie wymaga zabijania żadnych procesów.
Wydaje się, że po prostu przechodząc przez bieżące aktywne procesy i w jakikolwiek sposób „uzyskując dostęp” do otwartego procesu programu Excel, wszelkie zbłąkane wiszące wystąpienia programu Excel zostaną usunięte. Poniższy kod po prostu sprawdza procesy o nazwie „Excel”, a następnie zapisuje właściwość MainWindowTitle procesu do łańcucha. Ta „interakcja” z procesem wydaje się powodować, że Windows łapie i przerywa zawieszone wystąpienie programu Excel.
Korzystam z poniższej metody tuż przed tym, jak dodatek, który rozwijam, kończy pracę, ponieważ uruchamia to zdarzenie zwalniające. Usuwa za każdym razem wszelkie wiszące wystąpienia programu Excel. Szczerze mówiąc, nie jestem do końca pewien, dlaczego to działa, ale działa dobrze dla mnie i może zostać umieszczone na końcu dowolnej aplikacji Excel bez martwienia się o podwójne kropki, Marshal.ReleaseComObject ani procesy zabijania. Byłbym bardzo zainteresowany wszelkimi sugestiami, dlaczego jest to skuteczne.
źródło
Myślę, że niektóre z nich to tylko sposób, w jaki środowisko obsługuje aplikacje Office, ale mogę się mylić. W niektóre dni niektóre aplikacje natychmiast czyszczą procesy, a w inne dni wydaje się czekać na zamknięcie aplikacji. Ogólnie rzecz biorąc, przestałem zwracać uwagę na szczegóły i po prostu upewniłem się, że pod koniec dnia nie ma żadnych dodatkowych procesów.
Poza tym, a może już upraszczam rzeczy, ale myślę, że możesz po prostu ...
Jak powiedziałem wcześniej, nie zwracam uwagi na szczegóły, kiedy proces Excel pojawia się lub znika, ale to zwykle działa dla mnie. Nie lubię też utrzymywać procesów Excela poza minimalnym czasem, ale prawdopodobnie jestem po prostu paranoikiem.
źródło
Jak niektórzy zapewne już napisali, nie jest ważne tylko, jak zamknąć Excela (obiekt); ważne jest także to, jak go otworzysz , a także rodzaj projektu.
W aplikacji WPF zasadniczo ten sam kod działa bez problemów lub z niewielkimi problemami.
Mam projekt, w którym ten sam plik Excel jest przetwarzany kilka razy dla różnych wartości parametrów - np. Parsowanie go na podstawie wartości z ogólnej listy.
Umieszczam wszystkie funkcje związane z programem Excel w klasie bazowej, a analizator składni w podklasę (różne parsery używają typowych funkcji programu Excel). Nie chciałem, aby Excel był otwierany i zamykany ponownie dla każdego elementu z ogólnej listy, więc otworzyłem go tylko raz w klasie podstawowej i zamknąłem w podklasie. Miałem problemy podczas przenoszenia kodu do aplikacji komputerowej. Wypróbowałem wiele wyżej wymienionych rozwiązań.
GC.Collect()
został już wdrożony wcześniej, dwa razy jak sugerowano.Potem zdecydowałem, że przeniesie kod do otwierania programu Excel do podklasy. Zamiast otwierać tylko raz, teraz tworzę nowy obiekt (klasa podstawowa) i otwieram Excel dla każdego elementu i zamykam go na końcu. Występuje pewien spadek wydajności, ale na podstawie kilku testów procesy Excela zamykają się bez problemów (w trybie debugowania), więc usuwane są również pliki tymczasowe. Będę kontynuować testowanie i napiszę więcej, jeśli otrzymam jakieś aktualizacje.
Najważniejsze jest to: Musisz również sprawdzić kod inicjujący, szczególnie jeśli masz wiele klas itp.
źródło