[ negyxo @ 19.05.2008. 14:35 ] @
Evo interesantno pitanje. Elem, razgovarajuci sa kolegom, rece mi da je uradio u Dispose metodi lock nad nekim objektom kako bi obezbedio thread-safety za taj objekat. Uglavnom, moja prva reakcija je bila da to mozda i nije bas najpametnije, ali pre nego sto smo nastavili diskusiju, rekoh sebi daj da proverim i na MSDN vidim da je to sasvim validno. E sad mene interesuje, kako onda GC cisti te objekte, tj. sta se desva sa GC-om ako se bas blokirajuci lock implementira u Dispose funkciji? Ja sam dosada mislio da GC kad krene sa ciscenjem ide do kraja, bez obzira na kod koji se izvrsava ali ocigledno da sam nesto propustio.
[ mmix @ 19.05.2008. 15:49 ] @
Zavisi ko poziva Dispose

Ako ti rucno disposujes objekat (pozivajuci Dispose() ili Close() koji poziva Dispose()) onda mozda ima neke svrhe, mada je realno losa praksa praviti klase tako da jedan thread kreira objekat a vise threadova poziva Dispose(). Kao prvo dispose logicki unistava objekat, i poziva se samo kad si siguran da ti vise nikad nigde ne treba, sto znaci da to cini poslednji thread koji ima referencu na njega, dakle ako ti pozoves dispose() iz jednog threada dok postoje zive reference u drugim threadovima ubijas objekat bez da ostali threadovi to znaju. Ako se dogodi da dva threada pozovu dispose nad istim objekom imas ozbiljan problem u arhitekturi programa i mozda je bolje da pukne i stavi ti to do znanja.

U drugoj varijanti kad se dispose poziva iz destruktora/finalizera nema potrebe za lockom, GC ima zaseban thread koji poziva finalizer-e i uvek je samo jedan thread i nema sanse da GC izazove dvostruk poziv u finalizer a kamoli paralelni poziv istog. A ako je GC ubacio finalizer u Queue to znaci da nigde drugde nema zivih referenci, samim tim sve je safe.

Tako da ja na primer nikad ne stavljam lock u dispose.
[ negyxo @ 19.05.2008. 16:00 ] @
Situacija je takva da se ne poziva Dispose, bar ne "rucno", nego se u jednoj metodi koristi isti objekat za lock, a s obzirom da se objekt kreira iz unmanaged dela onda niko ne referencira na njega (ovo mi je malo nejasno ali tako mi je kolega objasnio, mogu sutra proverti tacno sta se desava) i GC u jednom trenutku poziva Dispose, a to se nekad desi kada se jos izvrsava metoda tog objekta

Evo primer da bude jasnije:

Code:

Class Test
{
object SyncRoot;

void Foo()
{
  lock(SyncRoot)
  {
     ....
  }
}

void Dispose()
{
  lock(SyncRoot)
  {
    ....    
   // cinimi se da ovde negde stavljaju i GC.KeepAlive(this)
  }
}


Ovo sam napisao iz glave, tj. ono sto sam skontao iz price, nemam code, uglavnom moja prva reakcija je da to mozda nije dobro jer bi onda lock blokirao dispose, a kao sto rekoh, mislio sam da GC radi posao do kraja, kada jednom krene sa kolekcijom odredjene generacije da se ne zaustavlja dok ne zavrsi.

Ukratko to je to, kasnije mozemo nastaviti, sada je kraj radnog vremena

[Ovu poruku je menjao mmix dana 19.05.2008. u 17:11 GMT+1]
[ mmix @ 19.05.2008. 16:38 ] @
Mislim da je tvoj kolega malo pogresno skapirao sve ovo

Prvo, manged objekti ne mogu direktno da se kreiraju iz unmanaged koda. Cak i kad to izgleda tako (npr iz C++-a), uvek postoji context-switch wrapper koji prebacuje iz jednog rezima u drugi.
Dalje, GC.KeepAlive(this) je potpuno nepotrebno u bilo kom kontekstu posto GC sasvim sigurno nece unistiti objekat dok je neki thread unutar koda tog objekta, taman posla da je to moguce morao bi na kraj svakog metoda da stavis KeepAlive . KeepAlive se koristi kad hoces da se osiguras da neki DRUGI objekat opstane do odredjenog trenutka u nekoj metodi i verovao ili ne KeepAlive ne radi apsolutno nista, blanko, on samo postoji da bi se GC prevario da postoji jos upotrebi odredjene reference te da je ne bi unistio pre vremena. Pogledaj opis KeepAlive metode u MSDNu za primer gde se to koristi.

Trece, lock u Dispose() ne moze da blokira GC, GC uvek ide kroz stablo generacije i redi jednu od tri stvari:
1. ako ima zivih referenci ostavlja objekat na miru i promovise ga u sledecu generaciju
2. ako nema referenci unistava referencu i oslobadja memoriju AKO i SAMO AKO je objekat zadovoljava jednu od tri stavke
a) objekat je value type kreiran na managed heap-u
b) objektu je u nekom trenutku bio pozvan SuppressFinalize()
c) objekat kojem je odradjen finalize() (vidi 3.)
3. ako nema referenci i ako finalizer nije suspendovan, prebacuje finalizer tog objekat u finalizer queue ako vec nije tamo i ignorise objekat do sledeceg ciklusa kad ce taj objekat uz malo srece potpasti pod 2c.

lock u dispose() moze teroijski samo da blokira GC finalizer thread, sto nije bas pametno jer usporava isti sto je tebi zapalo za oko. Medjutim to se nikad nece desiti jer da bi finalizer thread pozvao finalize tog objekta on prvo mora da bude bez zivih reference, sto ce reci nijedan thread tada ne moze da pozove Foo() jer nema referencu



[EDIT]
Samo mala izmena, znam da me nesto mucilo oko ovoga, zapravo KeepAlive(this) ima primenu i to samo u retkim situacijama kad iz metode objekta pozivas staticki metod (dakle napustas this), u toj situacji GC moze da pocisti objekat (this) i pre nego sto staticki metod pocne da radi (pod uslovom da se nista vise ne desava posle poziva statickog metoda) sto moze da izazove problem ako objekat sadrzi unmanaged resurs koji se prebacuje statickoj metodi (za managed resurs nema problema, GC onda nece ocistiti objekat jer je prebacena ziva referenca) i onda ne-staticki metod mora da pozove KeepAlive(this) da bi osigurao svoje prezivljavanje i sprecio svoje unistenje dok staticka metoda ne obavi svoje sa unmanaged resursom. To je mislim jedina konkretna primena.

[Ovu poruku je menjao mmix dana 19.05.2008. u 18:00 GMT+1]
[ negyxo @ 19.05.2008. 17:03 ] @
OK, kljucno je ovo

Citat:

lock u dispose() moze teroijski samo da blokira GC finalizer thread, sto nije bas pametno jer usporava isti sto je tebi zapalo za oko. Medjutim to se nikad nece desiti jer da bi finalizer thread pozvao finalize tog objekta on prvo mora da bude bez zivih reference, sto ce reci nijedan thread tada ne moze da pozove Foo() jer nema referencu


Izgleda da mi je to promaklo. Poseban thread za finalizer, odnosno freachable queue. Mada, vidis, na MSDN pise da je to OK, to je ono sto me isprva bunilo http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx

Inace, pogladacu sutra detalje, trazicu code review :), pa cemo u dalju diskusiju.

[Ovu poruku je menjao negyxo dana 19.05.2008. u 18:14 GMT+1]
[ mmix @ 19.05.2008. 17:27 ] @
Videcemo, morao bi da okacis taj Dispose da vidimo, ako je taj metod ispravno implementiran, zadnja linija koja ce biti izvrsena u Dispose je nesto nalik "_disposed = true;" sto referencira this i samim tim nema potrebe za GC.KeepAlive(this).

[ negyxo @ 20.05.2008. 18:50 ] @
Evo da se javim. Pricao sam jutros sa kolegom, dao sam mu link na ovu temu, pa mozda se javi

Inace veoma interesantno ovo za GC.KeepAlive, nisam znao da GC moze da pocisti objekat koji se nalazi u bloku koji je jos u toku izvrsavanja. Evo jedan interesantan link, gde je bas prikazn takav neki scenario gde resurs (_handle iz ovog linka) moze biti zatvoren a da se metod jos izvrsava http://blogs.msdn.com/cbrumme/archive/2003/04/19/51365.aspx, cudno nema sta.
[ mmix @ 20.05.2008. 22:27 ] @
Citat:
negyxo: Inace veoma interesantno ovo za GC.KeepAlive, nisam znao da GC moze da pocisti objekat koji se nalazi u bloku koji je jos u toku izvrsavanja.


To teorijski moze da se desi samo u release JIT/GC-u i to samo u situaciji kakva je ova iz brummeovog bloga. U praksi takva situacija je veoma veoma retka i moze se slobodno smatrati losim kodiranjem jer ces operacije nad unmanaged resursim a u potpunosti enkapsulirati u instancu disposable klase bez koriscenja static metoda (sta vise uopste nije preporucljivo da koristis static metode u ovu svrhu zbog ThreadSafety-a), te ce se this setati medju pozivima tako da nema bojazni da ce biti pociscen pre vremena. Ako malo proceprkas po frameworku videces da ni sam MS ne koristi KeepAlive(this) u Dispose() metodama (npr pogledaj WaitHandle klasu kroz reflector). Imaj takodje u vidu da Dispose/Close koji cisti unmanaged resurs "treba" na kraju da pozove GC.SuppressFinalize(this) sto u smislu GC-a i produzenja zivota ima isti efekat kao KeepAlive()
[ negyxo @ 20.05.2008. 23:42 ] @
mmix, ja to napisah kao zanimljiv podatak a ne da opravdavam koriscenje GC.keepAlive u Dispose metodi Na to sam naleteo kada sam resio podrobno da izucim sta radi tacno KeepAlive