Wywoływanie destruktora na istniejącym obiekcie?

Odpowiedz Nowy wątek
2014-09-21 18:35
fasfsaf
0

Napisałem coś w rodzaju web crawlera (nie do żadnych niecnych czynów). Ma on przez pewien czas trzymać w pamięci pewne informacje i w zależności od tego co znajdzie później musi zostawić część poprzednich informacji i wyrzucić resztę które się przedawniły.
Podczas prób programu szybko się okazało że potrzebuję trzymać w pamięci jednocześnie ponad 8 GB danych które się stale wymieniają.
W związku z tym używanie zwykłej pamięci stało się dla komputera i dla mnie zbyt uciążliwe więc postanowiłem zrzucać treść informacji na dysk a w obiekcie trzymać tylko nazwę pliku.

Obiekt trzyma nazwę pliku, w destruktorze (~Obiekt) napisałem funkcję która usuwa plik związany z obiektem.

W uproszczeniu klasa wygląda tak:

public Obiekt(MemoryStream m)
{
  this.fileName = LosowaNazwa() + (StatycznyLicznik ++);
  SaveToDisc(m, this.fileName);  
}

~Obiekt()
{
  if (File.Exists(this.fileName))
    File.Delete(this.FileName);
}

obiekty przechowuję w kolejce, a gdy chce usunąć pliki to po prostu usuwam obiekt z kolejki
Podczas działania programu okazało się że pliki są usuwane zanim je usunę z kolejki w losowym momencie - sprawdzałem pod debugerem i nie ma nigdzie usuwania tych obiektów z kolejki - pliki natomiast są usuwane

Dałem breakpoint na destruktor i okazuje się że ten jest wywoływany - nie mogę się dowiedzieć dlaczego bo stack nic nie wykazuje poza wątkiem garbage collectora
Czy jest możliwe że program przy przekazywaniu obiektu do różnych funkcji tworzy kopię obiektu, zamiast działać na oryginale i próbując pozbyć się tej kopii wywołuje destruktor który wskazuje na ten sam plik?

Jestem pewien że nie ma kolizji w nazwie pliku i że nie tworzę jednego obiektu dwa razy a także że nie usuwam ich nigdzie (cały czas istnieje twarda referencja w kolejce)

Próbowałem zmienić destruktor na IDisposable i to rozwiązało problem - pliki nie znikają kiedy nie powinny; ale tu z kolei zostają zbyt długo - metoda Dispose wydaje się nie być wywoływana za każdym razem, lub jej wywołanie jest bardzo opóźnione - nie jest również wykonywana przed wyłączeniem na siłę programu w związku z czym na dysku twardym zalegają niepotrzebne pliki

Pozostało 580 znaków

2014-09-21 18:53
0

W C# nie istnieje pojęcie destruktora. To co wygląda jak destruktor w C++, w C# jest finalizerem i jest odpalane przez Garbage Collector (nad czym generalnie nie masz jak zapanować). Jeśli GC zauważy, że obiekt nie jest już nigdzie wykorzystywany, albo zaczyna brakować pamięci, wówczas zaczyna sprzątać, odpalając finalizery w takich obiektach.
Do tego co Ty potrzebujesz, powinieneś wykorzystać IDisposable i metodę Dispose (co de facto zrobiłeś). Jeśli uznasz, że dany obiekt już nie będzie potrzebny (czyli przy usuwaniu z kolejki), wywołaj na nim Dispose. Wówczas plik zostanie usunięty. Po wywołaniu Dispose na obiekcie, pamiętaj o przypisaniu do zmiennej null.


itmichal.wordpress.com/

Pozostało 580 znaków

2014-09-21 19:03
fasfsafs
0
michal84 napisał(a):

Jeśli GC zauważy, że obiekt nie jest już nigdzie wykorzystywany, albo zaczyna brakować pamięci, wówczas zaczyna sprzątać, odpalając finalizery w takich obiektach.

Pytanie brzmiało dlaczego finalizer jest odpalany gdy żaden z powyższych warunków nie jest spełniony
W tej chwili używam metody Dispose, ale tak jak pisałem - nie jest ona odpalana choćby wtedy gdy wyłączam program przez menedżer zadań

Pozostało 580 znaków

2014-09-21 19:06
0

A wywołujesz Dispose z kodu? Czy polegasz na tym, że CLR sam ją wywoła?
Zamykanie aplikacji przez manager zadań jest tak na prawdę zabijaniem procesu, a nie zamykaniem aplikacji. W takiej sytuacji Dispose nie jest niestety wywoływany.


itmichal.wordpress.com/

Pozostało 580 znaków

2014-09-21 19:08
fasfsafs
0

dobrze - z tym się już mogę uporać - nie jest to problemem

temat tyczy się bardziej kwestii technicznej i mojej ciekawości na temat tego dlaczego jest odpalany finalizer w momencie w którym nie powinien być

Pozostało 580 znaków

2014-09-21 20:11
0

Nie mogę sobie sytuacji w której destruktor wywołbby się szybciej niż powinien, chyba że Twój Obiekt to struktura, a nie klasa. Jeśli nie jest strukturą to pokaż więcej kodu.


░█░█░█░█░█░█░█░█░█░█░█░

Pozostało 580 znaków

2014-09-21 22:15
0

Podczas działania programu okazało się że pliki są usuwane zanim je usunę z kolejki w losowym momencie - sprawdzałem pod debugerem i nie ma nigdzie usuwania tych obiektów z kolejki - pliki natomiast są usuwane

Pics or it didn't happen.

Czym jest ta twoja kolejka (jaka klasa) i jak zdefiniowana (zmienna lokalna w funkcji, pole klasy..?)

Pozostało 580 znaków

2014-09-22 19:13
0

Co do destruktorów to nie są one odpalane w momencie zerwania ostatniej referencji działa tutaj mechanizm trochę bardziej "subtelny".
Po zgubieniu ostatniej referencji obiekt sobie żyje aż GC stwierdzi że jednak nie jest potrzebny.
GC zauważa jednak że obiekt ma Destruktor i nie usuwa go z pamięci tylko wrzuca do specjalnej kolejki.
Istnieje sobie pewien wątek który sobie zagląda do tej kolejki i jak coś w niej jest to bierze to i wywołuje destruktor.

W C# istnieje tylko jedna uzasadniona przyczyna do pisania Destruktorów:
Jeżeli tworzysz klasę która implementuje IDisposable i nie masz pewności że ty sam oraz inni programiści korzystający z tej klasy wywołują na niej w odpowiednim momencie Dispose (czy to ręcznie czy to przez using) to w destruktorze piszesz kod który wygląda tak:
if (DisposeNieWywolany)
{
Dispose();
}
Ogólnie stosuj IDisposable i wywołuj go ręcznie (chyba że nie masz pewności że to ostatnie odwołanie do obiektu).


szogun
nie ręcznie, tylko przez using! a jeśli to pole klasy, to i nasza klasa powinna być IDisposable i wywoływać wszystkie Dispose w jej Dispose. - Azarien 2014-09-22 21:38
usinga używa się w jeżeli zwalniasz obiekt w tym samym bloku kodu (najczęściej do zmiennej lokalnej) pozwala to oszczędzić sporo linii kodu na poprawne zwolnienie przez try/finally kolega wyciąga obiekty z kolekcji (mówi że z kolejki) w tym przypadku Dispose zawołany jawnie jest po prostu czytelniejszy. - szogun1987 2014-09-24 18:48
albo w using albo w finally albo w Dispose(bool) inaczej prosi się o błąd. - krwq 2014-09-24 20:49
Na moje oko sytuacja wygląda podobnie do zwalniania kontrolek w dużych aplikacjach WinForms. Punkt kreacji zarówno w czasie jak i w kodzie jest oddalony od punktu zwolnienia elementu, dlatego ręczne wywołanie Dispose jest tu najlepszym rozwiązaniem, ponieważ wartość dodana jaką wnosi using (zabezpieczenie przed wywołaniem Dispose na null i zapewnienie zwolnienia obiektu nawet w przypadku wyjątku) tutaj nie występuje (ryzyko nie zwolnienia obiektu jest takie samo), natomiast użycie Dispose jest w tym wypadku czytelniejsze. - szogun1987 2014-09-27 15:13

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0