[C# .NET] TreeView

Pagina: 1
Acties:

  • NDF82
  • Registratie: Januari 2002
  • Laatst online: 22-05 08:45

NDF82

Doomed Space Marine

Topicstarter
Hi all,

Ik heb een TreeView die ik vul met data uit een database. Deze TreeView is 3 niveau's diep. Wanneer ik mijn data ververs bouw ik de TreeView opnieuw op en vouw ik alle nodes uit die voor het verversen van de data ook al uitgevouwen waren.

C#:
1
2
3
4
5
6
7
8
9
10
Niveau 1 Node x
 |
  - Niveau 2 Node a
 |   |
 |    - Niveau 3 Node 1
  - Niveau 2 Node b
     |
      - Niveau 3 Node 1
     |
      - Niveau 3 Node 2


Voor de eerste twee niveau's heb ik twee ArrayLists aangemaakt. Van iedere node op niveau 1 die is uitgevouwen voeg ik de PK toe aan de ArrayList voor niveau 1. Voor niveau 2 geld hetzelfde. Wanneer ik de TreeView vervolgens opnieuw opbouw kijk ik bij het toevoegen van een node of zijn PK in de lijst voorkomt. Zo ja, dan vouw ik hem uit.

Het probleem is dat dit tergend langzaam is. Voordat ik de boom begin op te bouwen roep ik wel TreeView.BeginUpdate() aan en aan het einde TreeView.EndUpdate() maar hier lijkt TreeNode.Expand niet van onder de indruk te zijn. Hoewel ik de nodes niet zie tijdens de opbouw zie ik wel de verticale scrollbar bewegen. De opbouw van de boom duurt hierdoor enkele seconden ipv een fractie van een seconde, wanneer de nodes niet uitgevouwen worden.

Iemand een idee waaraan dit kan liggen?

Pentium 233MHz MMX + Diamond Monster 3D 3DFX Voodoo II


  • whoami
  • Registratie: December 2000
  • Laatst online: 16:52
Weet je wat er precies zo traag is?
Is het het opbouwen van je boom?
Is het het doorzoeken van de arraylists? (Kan je daar trouwens geen hashtables voor gebruiken, dat is gewoon sneller aangezien je daar direct het juiste element kan 'pakken' ipv door je arraylist te loopen).

https://fgheysels.github.io/


  • NDF82
  • Registratie: Januari 2002
  • Laatst online: 22-05 08:45

NDF82

Doomed Space Marine

Topicstarter
whoami schreef op 24 september 2004 @ 09:51:
Weet je wat er precies zo traag is?
Is het het opbouwen van je boom?
Is het het doorzoeken van de arraylists? (Kan je daar trouwens geen hashtables voor gebruiken, dat is gewoon sneller aangezien je daar direct het juiste element kan 'pakken' ipv door je arraylist te loopen).
Het opbouwen van de boom gaat snel. Maar zodra ik de nodes uitvouw gaat het langzaam. Een Hashtable heb ik al geprobeerd, maar daarin zit geen merkbaar verschil.

Pentium 233MHz MMX + Diamond Monster 3D 3DFX Voodoo II


  • whoami
  • Registratie: December 2000
  • Laatst online: 16:52
Ik zou wel eens dat stukje code willen zien waarmee je je tree opbouwt (ook als je hashtables gebruikt ipv arraylists).
Ik kan moeilijk geloven dat er geen performance verschil zit tsn hashtable en arraylist als je die hashtable goed gebruikt.

[ Voor 35% gewijzigd door whoami op 24-09-2004 10:03 ]

https://fgheysels.github.io/


  • pjvandesande
  • Registratie: Maart 2004
  • Laatst online: 21-05 14:59

pjvandesande

GC.Collect(head);

Meschien dat je ook even kan kijken naar,
code:
1
.BeginUpdate();
en
code:
1
.EndUpdate();

  • NDF82
  • Registratie: Januari 2002
  • Laatst online: 22-05 08:45

NDF82

Doomed Space Marine

Topicstarter
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//
// Start het bewerken van de ListView.
//
treeView1.BeginUpdate();
//treeView1.Scrollable = false;

//
// Expand
//
expandListDefinities.RemoveRange(0, expandListDefinities.Count);
expandListGroepen.RemoveRange(0, expandListGroepen.Count);

//
// De TreeNodes Disposen
//
foreach (ArtikelDefinitieGroepTreeNode aDGTN in treeView1.Nodes) 
{
    if (aDGTN.IsExpanded) 
    {
        expandListGroepen.Add(aDGTN.ArtikelDefinitieGroep.Naam);
    }

    foreach (ArtikelDefinitieTreeNode aDTN in aDGTN.Nodes) 
    {
        if (aDTN.IsExpanded) 
        {
            expandListDefinities.Add(aDTN.ArtikelDefinitie.Nummer);
        }

        foreach (BasisArtikelTreeNode bATN in aDTN.Nodes) 
        {
            bATN.Dispose();
        }

        aDTN.Dispose();
    }

    aDGTN.Dispose();
}

//
// De TreeView leeg maken.
//
treeView1.Nodes.Clear();

//
// Vervolgens de TreeView opnieuw vullen.
//
if (ArtikelDefinitieGroepen != null)
{
    foreach (ArtikelDefinitieGroepEntity aDG in ArtikelDefinitieGroepen) 
    {
        //
        // ArtikelDefinitieGroep toevoegen
        //
        ArtikelDefinitieGroepTreeNode aDGTN = new ArtikelDefinitieGroepTreeNode(aDG);
        aDGTN.ImageIndex         = Images.Images.ARTIKELGROEP_INDEX;
        aDGTN.SelectedImageIndex = Images.Images.ARTIKELGROEP_INDEX;
        treeView1.Nodes.Add(aDGTN);

        //
        // ArtikelDefinities binnen de groep toevoegen.
        //
        foreach (ArtikelDefinitieEntity aD in aDG.ArtikelDefinitie) 
        {
            ArtikelDefinitieTreeNode aDTN = null;

            if (aD.AccordeerStatus.Equals("Concept")) 
            {
                if (toolBarButton1.Pushed) 
                {
                    aDTN = new ArtikelDefinitieTreeNode(aD);
                    aDTN.ImageIndex         = Images.Images.ARTIKELDEFINITIE_CONCEPT_INDEX;
                    aDTN.SelectedImageIndex = Images.Images.ARTIKELDEFINITIE_CONCEPT_INDEX;
                    aDGTN.Nodes.Add(aDTN);
                }
            }
            else if (aD.AccordeerStatus.Equals("Geaccordeerd")) 
            {
                if (toolBarButton2.Pushed) 
                {
                    aDTN = new ArtikelDefinitieTreeNode(aD);
                    aDTN.ImageIndex         = Images.Images.ARTIKELDEFINITIE_GEACCORDEERD_INDEX;
                    aDTN.SelectedImageIndex = Images.Images.ARTIKELDEFINITIE_GEACCORDEERD_INDEX;
                    aDGTN.Nodes.Add(aDTN);
                }
            }
            else if (aD.AccordeerStatus.Equals("Vervallen")) 
            {
                if (toolBarButton3.Pushed) 
                {
                    aDTN = new ArtikelDefinitieTreeNode(aD);
                    aDTN.ImageIndex         = Images.Images.ARTIKELDEFINITIE_VERVALLEN_INDEX;
                    aDTN.SelectedImageIndex = Images.Images.ARTIKELDEFINITIE_VERVALLEN_INDEX;
                    aDGTN.Nodes.Add(aDTN);
                }
            }

            //
            // BasisArtikelen binnen de ArtikelDefinitie toevoegen.
            //
            if (aDTN != null) {
                foreach (BasisArtikelEntity bA in aD.BasisArtikel) 
                {
                    if (bA.AccordeerStatus.Equals("Concept")) 
                    {
                        if (toolBarButton5.Pushed) 
                        {
                            BasisArtikelTreeNode bATN = new BasisArtikelTreeNode(bA);
                            bATN.ImageIndex         = Images.Images.BASISARTIKEL_CONCEPT_INDEX;
                            bATN.SelectedImageIndex = Images.Images.BASISARTIKEL_CONCEPT_INDEX;
                            aDTN.Nodes.Add(bATN);
                        }
                    }
                    else if (bA.AccordeerStatus.Equals("Geaccordeerd")) 
                    {
                        if (toolBarButton6.Pushed) 
                        {
                            BasisArtikelTreeNode bATN = new BasisArtikelTreeNode(bA);
                            bATN.ImageIndex         = Images.Images.BASISARTIKEL_GEACCORDEERD_INDEX;
                            bATN.SelectedImageIndex = Images.Images.BASISARTIKEL_GEACCORDEERD_INDEX;
                            aDTN.Nodes.Add(bATN);
                        }
                    }
                    else if (bA.AccordeerStatus.Equals("Vervallen")) 
                    {
                        if (toolBarButton7.Pushed) 
                        {
                            BasisArtikelTreeNode bATN = new BasisArtikelTreeNode(bA);
                            bATN.ImageIndex         = Images.Images.BASISARTIKEL_VERVALLEN_INDEX;
                            bATN.SelectedImageIndex = Images.Images.BASISARTIKEL_VERVALLEN_INDEX;
                            aDTN.Nodes.Add(bATN);
                        }
                    }
                }
            }

            //
            // Expand
            //
            if (aDTN != null) 
            {
                if (expandListDefinities.Contains(aDTN.ArtikelDefinitie.Nummer)) 
                {
                    aDTN.Expand();
                }
            }
        }

        //
        // Expand
        //
        if (expandListGroepen.Contains(aDGTN.ArtikelDefinitieGroep.Naam)) 
        {
            aDGTN.Expand();
        }
    }
}

//
// Eindig het bewerken van de TreeView.
//
//treeView1.Scrollable = true;
treeView1.EndUpdate();


Wat ook raar is, wanneer ik TreeView.Scrollable op false zet, dat hij dan nog sneller opgebouwd wordt.

Edit: Indention

[ Voor 8% gewijzigd door NDF82 op 24-09-2004 11:32 ]

Pentium 233MHz MMX + Diamond Monster 3D 3DFX Voodoo II


  • EfBe
  • Registratie: Januari 2000
  • Niet online
NDF82 schreef op 24 september 2004 @ 09:49:
Hi all,

Ik heb een TreeView die ik vul met data uit een database. Deze TreeView is 3 niveau's diep. Wanneer ik mijn data ververs bouw ik de TreeView opnieuw op en vouw ik alle nodes uit die voor het verversen van de data ook al uitgevouwen waren.

Voor de eerste twee niveau's heb ik twee ArrayLists aangemaakt. Van iedere node op niveau 1 die is uitgevouwen voeg ik de PK toe aan de ArrayList voor niveau 1. Voor niveau 2 geld hetzelfde. Wanneer ik de TreeView vervolgens opnieuw opbouw kijk ik bij het toevoegen van een node of zijn PK in de lijst voorkomt. Zo ja, dan vouw ik hem uit.

Het probleem is dat dit tergend langzaam is.
Ja duh, een ArrayList heeft geen index mechanisme, dus die gebruikt een linear search. :) Hoe meer nodes in je tree, hoe trager. Voor ELKE node moet hij dus die complete lijst af.

Je moet je PKs in een hashtable opslaan. Dus als key de PK en als value null. Dan doe je ContainsKey(value) en heb je veel sneller informatie. Overigens doe je het niet slim, want hoe haal jij je node op? Je slaat toch gewoon alle nodes in 1 hashtable op, PK als key en als value de TreeNode.
Voordat ik de boom begin op te bouwen roep ik wel TreeView.BeginUpdate() aan en aan het einde TreeView.EndUpdate() maar hier lijkt TreeNode.Expand niet van onder de indruk te zijn. Hoewel ik de nodes niet zie tijdens de opbouw zie ik wel de verticale scrollbar bewegen. De opbouw van de boom duurt hierdoor enkele seconden ipv een fractie van een seconde, wanneer de nodes niet uitgevouwen worden.
Iemand een idee waaraan dit kan liggen?
Expand is gewoon traag. De treeview is uberhaupt een verschrikkelijk slecht geschreven control en dan druk ik me nog zacht uit. Werkelijk onbeholpen trieste software (nou ja, software, TROEP is beter). Het is nl. geschreven bovenop de win32 treeview (die meer kan ook nog...) met messages. Nu is win32 asynchroon, en .net synchroon. heeee dat gaat niet werken, maar de wizards van de winforms group snappen dat niet.

Als je nl BeginUpdate() doet, daarna nogal wat dingen aan je tree wijzigt en dan VOORdat je EndUpdate() doet Application.DoEvents() aanroept heb je kans dat je complete app vastslaat. Nee, prima spul :)

Tot zover de rant, als je de kans hebt een 3rd party treeview te gebruiken, DOE HET. Het bespaart je veel tijd en grijze haren.

(edit).
back indent even je code, die staat onnodig 4 tabs naar rechts :)

en waarom doe je in vredesnaam dit: // De TreeNodes Disposen.
Is nergens voor nodig. Ik zie aan je code dat je ook elke keer je tree leegkiepert. Ook niet nodig als je de nodes bewaart tezamen met je PK, je kunt dan nodes modificeren, scheelt nogal.

(edit2)
.Equals() op strings hoeft niet. Je kunt gewoon var = "value"; doen. Ook gebruik je nested ifs, je kunt beter switch case gebruiken met string waarden. De C# compiler maakt daar dan een mooie static hashtable van die erg snel de juiste branch vindt.

Edit3: De expand in the inner foreach is overbodig. Je kunt recursive expanden in de buitenste loop (of nog beter: gewoon alles expanden na de loop). Ik denk dat wanneer je 1 recursive expand doet (ExpandAll()) je erg veel tijd bespaart :)

[ Voor 18% gewijzigd door EfBe op 24-09-2004 11:31 ]

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • NDF82
  • Registratie: Januari 2002
  • Laatst online: 22-05 08:45

NDF82

Doomed Space Marine

Topicstarter
EfBe schreef op 24 september 2004 @ 11:23:
(edit2)
.Equals() op strings hoeft niet. Je kunt gewoon var = "value"; doen. Ook gebruik je nested ifs, je kunt beter switch case gebruiken met string waarden. De C# compiler maakt daar dan een mooie static hashtable van die erg snel de juiste branch vindt.
Ok, dat zal ik eens proberen, maar daar ligt het performance probleem op dit moment niet.
EfBe schreef op 24 september 2004 @ 11:23:
Edit3: De expand in the inner foreach is overbodig. Je kunt recursive expanden in de buitenste loop (of nog beter: gewoon alles expanden na de loop). Ik denk dat wanneer je 1 recursive expand doet (ExpandAll()) je erg veel tijd bespaart :)
De performance problemen zitten volgens mij ook niet in loop vs. recursive, maar in het feit dat de treeview op één of andere manier zichzelf gedeeltelijk repaint tijdens het expanden van een treenode. Waneer ik de nodes niet expand gaat het snel, maar zodra ik de nodes één voor één expand (zoals in de loops) gaat het echt langzaam (enkele seconden).

Edit: een third party TreeView zit er helaas niet in. Ik loop stage dus heb hier bijna niets over te zeggen. Je moest eens weten wat ik heb moeten zeuren om een bepaalde O/R mapper aan te mogen aanschaffen ;)

[ Voor 10% gewijzigd door NDF82 op 24-09-2004 11:45 ]

Pentium 233MHz MMX + Diamond Monster 3D 3DFX Voodoo II


  • EfBe
  • Registratie: Januari 2000
  • Niet online
NDF82 schreef op 24 september 2004 @ 11:40:
[...]

Ok, dat zal ik eens proberen, maar daar ligt het performance probleem op dit moment niet.
Nee denk ik ook niet, maar maakt je code wat overzichtelijker :)
[...]
De performance problemen zitten volgens mij ook niet in loop vs. recursive, maar in het feit dat de treeview op één of andere manier zichzelf gedeeltelijk repaint tijdens het expanden van een treenode. Waneer ik de nodes niet expand gaat het snel, maar zodra ik de nodes één voor één expand (zoals in de loops) gaat het echt langzaam (enkele seconden).
Ik denk wel dat dit het is. Punt is: wanneer je een expand per node doet in een loop (en jij expand elke node afzonderlijk) stuurt hij elke keer een message, het duurt dan een tijdje voordat deze is verwerkt. Beter is om gewoon een expandall te doen wanneer je klaar bent met de tree, want de treecontrol gaat dan met 1 message intern alles expanden, wat veel efficienter is. (ik ben weken bezig geweest om die treeview in de project explorer efficient te krijgen... op een gegeven moment maar 'on the fly' node addition geimplementeerd, dus wanneer je een node opent dat dan de subnodes worden toegevoegd en wanneer je hem sluit dat de nodes weer worden weggehaald. )
Edit: een third party TreeView zit er helaas niet in. Ik loop stage dus heb hier bijna niets over te zeggen. Je moest eens weten wat ik heb moeten zeuren om een bepaalde O/R mapper aan te mogen aanschaffen ;)
:) Ik herkende al iets ;).

[ Voor 11% gewijzigd door EfBe op 24-09-2004 12:29 ]

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • NDF82
  • Registratie: Januari 2002
  • Laatst online: 22-05 08:45

NDF82

Doomed Space Marine

Topicstarter
EfBe schreef op 24 september 2004 @ 12:27:
(ik ben weken bezig geweest om die treeview in de project explorer efficient te krijgen... op een gegeven moment maar 'on the fly' node addition geimplementeerd, dus wanneer je een node opent dat dan de subnodes worden toegevoegd en wanneer je hem sluit dat de nodes weer worden weggehaald. )
Ik zat daar vanmorgen ook aan te denken...

Ik heb het inmiddels verholpen met behulp van een trial versie van een profiler (best handig, nog nooit eerder gebruikt). De fout zat in het disposen van de TreeNodes |:(. Ik had zelf deze method toegevoegd aan de TreeNode om bepaalde Events te verwijderen. Ik was echter al lang van Events binnen een TreeNode afgestapt. Wat er wel gebeurde in de Dispose() method was het object op null zetten dat de TreeNode representeerde. In de set methode werd echter ook de Text property van de TreeNode geupdate. Wanneer (bijna) alle nodes uitgevouwen waren, moest dus voor alle nodes een repaint gedaan worden :X

Pentium 233MHz MMX + Diamond Monster 3D 3DFX Voodoo II


  • EfBe
  • Registratie: Januari 2000
  • Niet online
Hmm, inderdaad vrij ranzig ... (nprof is overigens een goede gratis profiler)

Overigens kun je gewoon Dispose achterwege laten wanneer je niet iets met database connecties doet of unsafe code. Treenodes disposen zichzelf wanneer ze out of scope gaan net zoals alle andere .NET classes :) (alleen bv bij een ado.net connection wil je de unsafe connection meteen terughebben, vandaar dispose in die situatie)

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
De treeview control is niet erg snel dus je moet zo min mogelijk aanpassingen tegelijkertijd doen om het nog een beetje vlot te houden. Je performance probleem in deze situatie is dat je iedere keer een full refresh van de treeview doet. Dat is erg langzaam bij veel items en weinig aanpassingen, je kunt beter testen wat er daadwerkelijk veranderd is en dat aanpassen.

Bij performance problemen is het idd altijd handig om een profiler bij de hand te hebben. NProf is goed voor CPU tijden, ik vind zelf de CLR Profiler erg handig om memory leaks of heap thrashing op te sporen.

Er worden hier een paar dingen gesteld die ik toch even wil nuanceren:
Ook gebruik je nested ifs, je kunt beter switch case gebruiken met string waarden. De C# compiler maakt daar dan een mooie static hashtable van die erg snel de juiste branch vindt.
Merk wel op dat een hashmap lookup afhankelijk van de items in de hashmap pas bij een bepaald aantal items ook echt sneller wordt, omdat de kosten per stap bij een hashmap lookup hoger zijn dan een lineaire search. Bij strings ligt dit getal op ongeveer 8, dus hopelijk zal de compiler bij 3 vergelijkingen niet kiezen voor een hashmap, anders is deze wijziging juist negatief voor performance. (Dit gaat trouwens wel allemaal over nanosecondes verschil).
Overigens kun je gewoon Dispose achterwege laten wanneer je niet iets met database connecties doet of unsafe code.
Dat is niet helemaal waar. De relatie tussen unsafe code is een beetje vaag, het gaat in feite om de systeem resources die je vasthoudt met je objecten (en systeem resources worden meestal unsafe gealloceerd). Systeem resources zijn niet alleen database connecties, maar ook bijvoorbeeld file streams of (in dit geval) GDI resources. Eigenlijk kun je ervan uit gaan dat ieder object dat een Dispose methode heeft deze methode niet voor niets heeft, er zal dan een schaarse systeem resource door het object vastgehouden worden, en er zijn situaties waarbij je die zo snel mogelijk vrij wilt geven.

Normaliter hoef je zelf GDI resources niet te managen omdat je alles in een form stopt, en die regelt het dan wel. Als je zelf echter controls gaat schrijven of dynamisch manipuleert dan kan het wel degelijk handig zijn om zelf te disposen.

  • EfBe
  • Registratie: Januari 2000
  • Niet online
misfire schreef op 25 september 2004 @ 10:33:
De treeview control is niet erg snel dus je moet zo min mogelijk aanpassingen tegelijkertijd doen om het nog een beetje vlot te houden. Je performance probleem in deze situatie is dat je iedere keer een full refresh van de treeview doet. Dat is erg langzaam bij veel items en weinig aanpassingen, je kunt beter testen wat er daadwerkelijk veranderd is en dat aanpassen.
Dat zegt ie: de dispose calls verzorgden een redraw :)
Er worden hier een paar dingen gesteld die ik toch even wil nuanceren:
[...]
Merk wel op dat een hashmap lookup afhankelijk van de items in de hashmap pas bij een bepaald aantal items ook echt sneller wordt, omdat de kosten per stap bij een hashmap lookup hoger zijn dan een lineaire search. Bij strings ligt dit getal op ongeveer 8, dus hopelijk zal de compiler bij 3 vergelijkingen niet kiezen voor een hashmap, anders is deze wijziging juist negatief voor performance. (Dit gaat trouwens wel allemaal over nanosecondes verschil).
Ik heb geen idee wanneer de compiler kiest voor een hashtable, maar ik denk dat ze bij het compiler team wel weten wanneer dat het beste kan :)
[dipose blabla]
Dat is niet helemaal waar. De relatie tussen unsafe code is een beetje vaag, het gaat in feite om de systeem resources die je vasthoudt met je objecten (en systeem resources worden meestal unsafe gealloceerd). Systeem resources zijn niet alleen database connecties, maar ook bijvoorbeeld file streams of (in dit geval) GDI resources. Eigenlijk kun je ervan uit gaan dat ieder object dat een Dispose methode heeft deze methode niet voor niets heeft, er zal dan een schaarse systeem resource door het object vastgehouden worden, en er zijn situaties waarbij je die zo snel mogelijk vrij wilt geven.
Wanneer een object IDisposable implementeert dan zal de GC altijd Dispose() aanroepen, daarvoor hoef je niet bang te zijn. Hoe jij het stelt lijkt het alsof je dispose zelf moet aanroepen en OOK nog eens moet weten wanneer een object een unsafe resource vasthoudt! Dat is natuurlijk onzin. De garbage collector regelt dat voor je, alleen wanneer is niet te zeggen, vandaar dat je het handmatig kan doen (en bv soms moet doen zoals bij de Firebird .NET provider, anders worden de database resources niet vrijgegeven)
Normaliter hoef je zelf GDI resources niet te managen omdat je alles in een form stopt, en die regelt het dan wel. Als je zelf echter controls gaat schrijven of dynamisch manipuleert dan kan het wel degelijk handig zijn om zelf te disposen.
Nee, om zelf IDisposable te implementeren. Je draait het om, en dat hoeft niet.

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
Dit gaat weer hopeloos offtopic, maar wellicht ook wel interessante weetjes voor de topicstarter:
EfBe schreef op 25 september 2004 @ 11:02:Ik heb geen idee wanneer de compiler kiest voor een hashtable, maar ik denk dat ze bij het compiler team wel weten wanneer dat het beste kan :)
Ik ben niet tegen het gebruik van de switch constructie, omdat het gewoon duidelijker leest.

Als je echter als reden performance aanvoert dan is het handig om in het achterhoofd te houden dat óf het compiler team goed nagedacht heeft en er inderdaad geen hashmap wordt gebruikt, dus er is geen performance winst, óf er wordt toch een hashmap toegepast, met als netto resultaat een performance verlies. In deze situatie is performance dus geen goed argument voor een switch constructie.
Wanneer een object IDisposable implementeert dan zal de GC altijd Dispose() aanroepen, daarvoor hoef je niet bang te zijn. Hoe jij het stelt lijkt het alsof je dispose zelf moet aanroepen en OOK nog eens moet weten wanneer een object een unsafe resource vasthoudt! Dat is natuurlijk onzin.
Daar kom je weer aan met je onzin. :( Nogmaals dat vind ik geen goede manier van discussiëren, zeker als je er zelf naast zit te kletsen.

De GC zal helemaal niet altijd Dispose aanroepen, de GC kijkt alleen of een object een finalizer heeft. In een correcte implementatie van het Disposable patroon zal de finalizer de Dispose aanroepen, maar dat heeft niets te maken met hoe en of de GC apart met de IDisposable interface om zou gaan.
Het using statement kijkt wel naar de IDisposable interface, en zal automatisch een stukje code maken dat expliciet de Dispose methode aanroept. Ook dat heeft dus niets met de GC te maken.

Overigens is het onverstandig om via de GC pas in de finalizer de Dispose() methode aan te laten roepen, omdat het negatief de perfomance beïnvloed. Het finalize mechanisme vertraagt het opruimen van de objecten door de GC met een extra stap, waardoor objecten langer moeten leven, en je dus eerder last van heap thrashing hebt. Als je in je eigen code de Dispose() aanroept dan zal bij een correcte implementatie van het Dipose patroon het object niet meer gefinalized worden vanwege een aanroep vanuit de dispose methode naar een systeem library waardoor de finalizer van het object zal worden genegeerd. Hierdoor kan de GC hem sneller opruimen en je heap schaalt dus beter.

Ik heb het helemaal niet over "unsafe resources" gehad, sterker nog, ik zei juist dat ik "unsafe" een te vage benoeming vond en daarom had ik het over systeem resources. Systeem resources zijn altijd schaars en daarom wil je zelf expliciet managen hoe lang je applicatie ze nodig heeft, om zo kort mogelijk resources vast te houden. Dat heeft niets te maken met unsafe code, je zou bijvoorbeeld ook een database in .net managed code kunnen bouwen, maar dan wil je nog steeds dat connecties zo snel mogelijk worden vrijgegeven, zodat je ze efficiënt kunt poolen.

Last but not least gaf ik aan dat een object niet voor niets een Dispose() methode heeft. Daarmee weet je dus dat je alert moet zijn op de mogelijke impact op de systeem resources. Ik heb helemaal niet gezegd dat je precies moet weten wat een object vasthoudt, en ik heb ook situaties geschetst waarbij het niet nodig is om de Dispose() methode zelf aan te roepen. Hoe jij bij de bovenstaande samenvatting van mijn verhaal komt is mij een raadsel.

  • EfBe
  • Registratie: Januari 2000
  • Niet online
Na een nachtje slapen wil ik wel even een toelichting geven op wat ik geschreven had hieronder.

Ik vind zelf de gang van zaken hoe Microsoft het dispose pattern of wat daar voor door moet gaan heeft aangepakt, bijzonder slecht. Waarom niet een IDisposable interface op Object implementeren, en waarin Dispose(bool) (indien overriden) de resource release verzorgt? Niks finalizers nodig. Want als je 2 methods nodig hebt die altijd aanwezig moeten zijn, kun je net zo goed 1 maken. Dispose aanroepen handmatig geeft de resources direct vrij, en anders doet de GC het voor je, klaar. Maar het hele idee achter Dispose is behoorlijk vaag. Immers, wat is het verschil tussen SqlConnection.Dispose() en SqlConnection.Close() in het kader van resource release? Niets. Na een Close() kun je niets meer, je moet eerst weer Open() aanroepen, wat een nieuwe connection uit de pool haalt. Na Dispose() kun je ook niets meer. Het is zelfs zo dat je na Dispose() helemaal niets meer mag doen met het object. Dit is foutgevoelig.

Tezamen met de eventhandler leak errors is IDisposable een van de grote designflaws van .NET: niemand weet hoe het echt werkt, wat je echt moet doen en waarom en waarom je UBERHAUPT dat moet weten, terwijl MS wel komt vertellen dat finalizers slecht zijn, traag zijn en moeten worden vermeden... erm.. ja en IDisposable dan? ....
misfire schreef op 25 september 2004 @ 13:59:
Dit gaat weer hopeloos offtopic, maar wellicht ook wel interessante weetjes voor de topicstarter:
[...]
Ik ben niet tegen het gebruik van de switch constructie, omdat het gewoon duidelijker leest.

Als je echter als reden performance aanvoert dan is het handig om in het achterhoofd te houden dat óf het compiler team goed nagedacht heeft en er inderdaad geen hashmap wordt gebruikt, dus er is geen performance winst, óf er wordt toch een hashmap toegepast, met als netto resultaat een performance verlies. In deze situatie is performance dus geen goed argument voor een switch constructie.
Equals is sowieso trager dan '=='. Dat boeit verder niet zo, voor die paar cycles, maar een switch case is gewoon netter.
[...]
Daar kom je weer aan met je onzin. :( Nogmaals dat vind ik geen goede manier van discussiëren, zeker als je er zelf naast zit te kletsen.
Erm, ik denk niet dat ik er naast zit, maar dat terzijde.
De GC zal helemaal niet altijd Dispose aanroepen, de GC kijkt alleen of een object een finalizer heeft. In een correcte implementatie van het Disposable patroon zal de finalizer de Dispose aanroepen, maar dat heeft niets te maken met hoe en of de GC apart met de IDisposable interface om zou gaan.
Maar, wie documentatie leest weet dat dat wel zo is:
Note that even when you provide explicit control by way of Dispose, you should provide implicit cleanup using the Finalize method. Finalize provides a backup to prevent resources from permanently leaking if the programmer fails to call Dispose.
uit Implementing Finalize and Dispose to clean up unmanaged resources.
Overigens is het onverstandig om via de GC pas in de finalizer de Dispose() methode aan te laten roepen, omdat het negatief de perfomance beïnvloed.
Een class met een unmanaged resource HEEFT een finalizer, en dus last van die dip.
Het finalize mechanisme vertraagt het opruimen van de objecten door de GC met een extra stap, waardoor objecten langer moeten leven, en je dus eerder last van heap thrashing hebt.
Van wat? Heap trashing? Dit is .NET, geen win32. Als ik met win32 crap bezig wil zijn, gebruik ik wel VC++ en COM en geen .NET. En waarom heap trashing?
Als je in je eigen code de Dispose() aanroept dan zal bij een correcte implementatie van het Dipose patroon het object niet meer gefinalized worden vanwege een aanroep vanuit de dispose methode naar een systeem library waardoor de finalizer van het object zal worden genegeerd. Hierdoor kan de GC hem sneller opruimen en je heap schaalt dus beter.
Ik snap niet wat die heap met het gebeuren te maken heeft, want je zit niet met de heap te praten maar met de CLR. Hoe de resources gemanaged worden intern is totaal buiten de scope van je probleem. Je KUNT dispose zelf aanroepen als explicit release method voor resources. Echter, dat is zelden nodig, want een unmanaged resource houdend object heeft ook een finalizer. Het is ook belachelijk dat je als consumer van een class moet weten welke unmanaged resouces deze intern vasthoudt. Dat gaat tegen het OO principe in.
Ik heb het helemaal niet over "unsafe resources" gehad, sterker nog, ik zei juist dat ik "unsafe" een te vage benoeming vond en daarom had ik het over systeem resources. Systeem resources zijn altijd schaars en daarom wil je zelf expliciet managen hoe lang je applicatie ze nodig heeft, om zo kort mogelijk resources vast te houden. Dat heeft niets te maken met unsafe code, je zou bijvoorbeeld ook een database in .net managed code kunnen bouwen, maar dan wil je nog steeds dat connecties zo snel mogelijk worden vrijgegeven, zodat je ze efficiënt kunt poolen.
Ooit gezien hoe ADO.NET connecties poolt? Juist, zodra jij Close() aanroept gaat die connectie terug in de pool. Niks wachten op dispose. En zo hoort het. Sluit je een file? opruimen die hap. Niet bij dispose. In dispose doe je emergency recovery: een transaction loopt nog en iemand roept dispose aan, oops.

Overigens zijn system resources helemaal niet 'schaars', zou niet best zijn.
Last but not least gaf ik aan dat een object niet voor niets een Dispose() methode heeft. Daarmee weet je dus dat je alert moet zijn op de mogelijke impact op de systeem resources. Ik heb helemaal niet gezegd dat je precies moet weten wat een object vasthoudt, en ik heb ook situaties geschetst waarbij het niet nodig is om de Dispose() methode zelf aan te roepen. Hoe jij bij de bovenstaande samenvatting van mijn verhaal komt is mij een raadsel.
En hoe moet een gebruiker van een class nu ruiken wanneer een dispose call 'wel schadelijk is voor die schaarse resources' (oef, maar 2gig aan handles!!) en zoals ze zelf zegt "het niet nodig is om dispose zelf aan te roepen" ? Juist, dat weet je niet. Dus wat te doen bij het zien van een IDisposable implementatie? ...

En nog even over die heap: misschien is de resource helemaal niet op de heap gealloceerd maar in mainmem, in een ander process, in een service of een memmapped file, weet jij het? maar belangrijker: boeit het?

Het enige dat boeit in .NET is het opruimen van eventhandlers. En DAT is nu juist iets wat bijna niemand zegt. Alleen daardoor krijg je memleaks, (en van niet geimplementeerde finalizers, ok). Laat de GC het fijn uitzoeken met die finalizers, daar zijn ze tenslotte voor.

[ Voor 12% gewijzigd door EfBe op 26-09-2004 11:46 ]

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
EfBe schreef op 25 september 2004 @ 22:45:Ik vind zelf de gang van zaken hoe Microsoft het dispose pattern of wat daar voor door moet gaan heeft aangepakt, bijzonder slecht. Waarom niet een IDisposable interface op Object implementeren, en waarin Dispose(bool) (indien overriden) de resource release verzorgt? Niks finalizers nodig. Want als je 2 methods nodig hebt die altijd aanwezig moeten zijn, kun je net zo goed 1 maken. Dispose aanroepen handmatig geeft de resources direct vrij, en anders doet de GC het voor je, klaar.
Dit verschil is er juist om te zien als gebruiker welke objecten zo snel mogelijk clean-up moeten doen. Jij ziet dit verschil liever niet, om op zich goede redenen, maar in de doelstellingen van .NET staat performance hoger dan encapsulatie. In de oplossing die jij beschrijft heeft ieder object een finalizer, dat zou gigantische impact op de performance hebben. Je zou niet meer kunnen zien welke objecten clean-up moeten doen, omdat ieder object een dispose methode heeft.

Je zou je wel kunnen afvragen waarom er een verschil is gemaakt tussen de finalizer en de dispose methode. Waarom niet gewoon de finalizer zelf aanroepen? Ik denk dat echter het verschil gemaakt is juist om aan te geven voor welke objecten dit wél nodig is. Als een object een finalizer heeft wil dat nog niet zeggen dat ie zo snel mogelijk moet worden opgeruimd.
Maar het hele idee achter Dispose is behoorlijk vaag. Immers, wat is het verschil tussen SqlConnection.Dispose() en SqlConnection.Close() in het kader van resource release? Niets. Na een Close() kun je niets meer, je moet eerst weer Open() aanroepen, wat een nieuwe connection uit de pool haalt. Na Dispose() kun je ook niets meer. Het is zelfs zo dat je na Dispose() helemaal niets meer mag doen met het object. Dit is foutgevoelig.
Je geeft zelf het verschil al aan: na close() mag je het object wél hergebruiken, na dispose() niet. Ik ben het met je eens dat dispose() in dit geval een beetje redundant is, maar het is geïntroduceerd om juist een stukje resource management generiek op te kunnen lossen. De naam Close() is niet voor iedere soort resource acceptabel, en ook niet iedere soort resource is her-openbaar. Daarom is de dispose() generiek genoeg gemaakt om voor iedere soort resource clean-up te kunnen dienen.
Tezamen met de eventhandler leak errors is IDisposable een van de grote designflaws van .NET: niemand weet hoe het echt werkt, wat je echt moet doen en waarom en waarom je UBERHAUPT dat moet weten, terwijl MS wel komt vertellen dat finalizers slecht zijn, traag zijn en moeten worden vermeden... erm.. ja en IDisposable dan? ....
.NET heeft precies hetzelfde probleem als iedere ander platform: als je niet weet hoe alles werkt dan kun je in gigantische problemen komen. Het IDisposable patroon is juist om te voorkomen dat de finalizer wordt aangeroepen, dus Microsoft spreekt zichzelf daar echt niet tegen. Jij vindt echter IDisposable een implementatie detail dat je liever niet gebruikt. Dat ziet Microsoft dus (terecht) wel anders.
Erm, ik denk niet dat ik er naast zit, maar dat terzijde.
Zit ik er naast dan? Als jij mij betrapt op een onwaarheid in dit verhaal hoor ik het graag.
[Finalize is niet hetzelfde als Dispose, de GC roept alleen de finalizer aan.]
Maar, wie documentatie leest weet dat dat wel zo is:

[Note that even when you provide explicit control by way of Dispose, you should provide implicit cleanup using the Finalize method. Finalize provides a backup to prevent resources from permanently leaking if the programmer fails to call Dispose.]

uit Implementing Finalize and Dispose to clean up unmanaged resources.
Het stukje documentatie klopt inderdaad, en beschrijft bijna letterlijk wat ik vertelde. Er staat heel duidelijk dat de finalizer van een disposable als backup moet dienen, en dat de programmeur hier zelf voor moet zorgen. Dat is dus net even iets minder kort door de bocht als "Wanneer een object IDisposable implementeert dan zal de GC altijd Dispose() aanroepen". De bewoordingen die gebruikt worden in dit stukje (en ik ben er zeker van dat in een andere alinea van deze help-pagina dit expliciet verteld wordt) geven ook aan dat de dispose aanroepen via de finalizer een backup, als de programmeur "gefaald" heeft.
[Finalizers vertragen de GC]

Een class met een unmanaged resource HEEFT een finalizer, en dus last van die dip.
Je hebt de volgende regel die ik over opgeschreven heb ook gequote, heb je hem ook gelezen? Hier legde ik uit dat de dispose methode er meestal voor zal zorgen dat de finalizer niet zal worden gebruikt in de GC, als de dispose methode al eerder is aangeroepen. Je hebt dus ALLEEN last van de performance dip als de dispose() NIET wordt aangeroepen in de code.
[Slomere GC betekent eerder last van heap thrashing]

Van wat? Heap trashing? Dit is .NET, geen win32. Als ik met win32 crap bezig wil zijn, gebruik ik wel VC++ en COM en geen .NET. En waarom heap trashing?

Ik snap niet wat die heap met het gebeuren te maken heeft, want je zit niet met de heap te praten maar met de CLR. Hoe de resources gemanaged worden intern is totaal buiten de scope van je probleem.
Sorry hoor, maar nu begin je wel een beetje kinderachtig te doen. Jij weet ook vast wel dat er zoiets als een managed heap bestaat, en die kun je net zo goed in de poeier helpen. Dat "managed" moet je wel met een korreltje zout nemen. Ik zie ook niet hoe dit nu buiten de scope van het probleem ligt. Het probleem was performance, en de performance van een applicatie kan echt dramatisch slechter zijn als je simpelweg niet goed met je objecten om gaat.
Je KUNT dispose zelf aanroepen als explicit release method voor resources. Echter, dat is zelden nodig, want een unmanaged resource houdend object heeft ook een finalizer.

Het is ook belachelijk dat je als consumer van een class moet weten welke unmanaged resouces deze intern vasthoudt. Dat gaat tegen het OO principe in.
Dat is dus bad practice. De finalizer is een vangnet, en hier moet niet expliciet gesteund op worden door de programmeur. Dat jij vindt dat het niet zo hoeven heeft de rest van de .NET wereld eerlijk gezegd weinig aan denk ik. Ik weet ook niet direct waarom een dispose() niet volgens OO zou zijn, en een close() wel. Een object is een eenheid van verantwoordelijkheid, en als in het contract van het object staat "roep de dispose zo snel mogelijk aan nadat je klaar met me bent, anders wordt het traag" dan zie ik niet zo goed hoe dit nu anti-OO is. Vervelend dat je er op moet letten, ben ik absoluut met je eens, maar dit is gewoon de realiteit.
[Dispose gaat om schaarse resources te managen]
Ooit gezien hoe ADO.NET connecties poolt? Juist, zodra jij Close() aanroept gaat die connectie terug in de pool. Niks wachten op dispose. En zo hoort het. Sluit je een file? opruimen die hap. Niet bij dispose. In dispose doe je emergency recovery: een transaction loopt nog en iemand roept dispose aan, oops.

Overigens zijn system resources helemaal niet 'schaars', zou niet best zijn.
Tja als je het zo bekijkt. Dat zou dus inhouden dat naast een finalizer en een dispose methode er nog een derde close() methode nodig is die alles opruimt. En is dat wel zo? Als een connectie zichzelf na een close() nog kan openen, heeft ie dan niet stiekem nog wat resources vast om dit te kunnen? Ik weet het niet, met dispose zit je altijd goed. De resources waar het bij dispose() om gaat zijn wél schaars, anders hoef je ze niet zo vroeg vrij te geven. Of stel jij het aantal connecties in je pool op oneindig in? Dan heb je niet zo veel meer aan je pool.
En hoe moet een gebruiker van een class nu ruiken wanneer een dispose call 'wel schadelijk is voor die schaarse resources' (oef, maar 2gig aan handles!!) en zoals ze zelf zegt "het niet nodig is om dispose zelf aan te roepen" ? Juist, dat weet je niet. Dus wat te doen bij het zien van een IDisposable implementatie? ...
Heel simpel: als ie disposable is, dan moet je er altijd voor zorgen dat de dispose() wordt aangeroepen. Jij roept "maar 2 gig aan handles", nou als een applicatie dat ook daadwerkelijk alloceert dan merk je wel degelijk dat dat niet zo handig is. Je hoeft niet precies te weten wat er allemaal onder water gebeurt, je hoeft alleen te weten dat je netjes disposed, en dan zal het allemaal in de meeste gevallen wel loslopen.
Het enige dat boeit in .NET is het opruimen van eventhandlers. En DAT is nu juist iets wat bijna niemand zegt. Alleen daardoor krijg je memleaks, (en van niet geimplementeerde finalizers, ok). Laat de GC het fijn uitzoeken met die finalizers, daar zijn ze tenslotte voor.
Tja event handlers zijn natuurlijk weer een totaal andere discussie. En als jij het zo vervelend vind om dispose te doen dat je alles vertraagt door het de finalizers op te laten knappen, be my guest. Als je maar even een dikke disclaimer bij zet: Dit is omdat EfBe dit mooier vindt, maar is géén practice die Microsoft aanbeveelt en Het is géén onzin als je toch zelf de dispose aanroept, want dat is wel sneller.. ;) Dit is wel een openbaar forum, dus ik vind dat je de verantwoordelijkheid hebt om dingen te vertellen zoals ze zijn, niet zoals je ze graag zou willen hebben.

  • EfBe
  • Registratie: Januari 2000
  • Niet online
misfire schreef op 26 september 2004 @ 13:49:
[...]
Dit verschil is er juist om te zien als gebruiker welke objecten zo snel mogelijk clean-up moeten doen.
Jij zei dat het soms niet hoeft. Wanneer niet is niet duidelijk dan want je hebt alleen maar een interface op een class.
Jij ziet dit verschil liever niet, om op zich goede redenen, maar in de doelstellingen van .NET staat performance hoger dan encapsulatie. In de oplossing die jij beschrijft heeft ieder object een finalizer, dat zou gigantische impact op de performance hebben. Je zou niet meer kunnen zien welke objecten clean-up moeten doen, omdat ieder object een dispose methode heeft.
Nee, niet ieder object heeft een finalizer, alleen de classes die een unsafe resource referencen hebben een IDisposable implementatie en de GC roept dan de Dispose() routine aan wanneer het object outofscope gaat. Als je dat zelf al gedaan hebt, doet die dus niet veel meer. Veel simpeler. Overigens doe jij net alsof een finalizer hebben megatraag is, wat onzin is, het scheelt een paar cycles.
Je zou je wel kunnen afvragen waarom er een verschil is gemaakt tussen de finalizer en de dispose methode. Waarom niet gewoon de finalizer zelf aanroepen? Ik denk dat echter het verschil gemaakt is juist om aan te geven voor welke objecten dit wél nodig is. Als een object een finalizer heeft wil dat nog niet zeggen dat ie zo snel mogelijk moet worden opgeruimd.
Nou als dat de reden is dan is dat een behoorlijke domme. De complete FCL is opgezet met in het achterhoofd: create en de GC ruimt het wel op. Nu moet je dan ineens bij elke class de IDisposable interface implementatie gaan nakijken? Lijkt me een onzinnige eis, want je verzandt dan in het aloude pattern wat ook in C++ gebruikt wordt, nl. dat je al je pointers moet deleten die je new't. Sterker, met smart pointers heb je het MAKKELIJKER in C++ dan in .NET op die manier. Ergo: onzin: creeer de objects, en wanneer ze outofscope gaan ruimt de GC ze op. Bedien je wel van Close() wanneer je een open doet, en that's it.
[...]
Je geeft zelf het verschil al aan: na close() mag je het object wél hergebruiken, na dispose() niet.
Van wie mag ik dat niet? Zie je niet hoe fout die visie is die erachter zit... Ik roep een method aan op een object en daarna mag ik het object niet meer gebruiken, maar het is er nog wel...
Ik ben het met je eens dat dispose() in dit geval een beetje redundant is, maar het is geïntroduceerd om juist een stukje resource management generiek op te kunnen lossen.
Maar waarom moet ik me daarmee bezig houden? Ten eerste WEET ik helemaal niet of de class resources in zich heeft en OF dat een dispose call vereist (ik heb de source niet) en ten tweede heb ik de GC die het voor me regelt, waarom moet ik me bezig houden met resource cleaning? Kan ik beter C++ gaan programmeren met smart pointers en COM, want dat is EN sneller, EN makkelijker.
De naam Close() is niet voor iedere soort resource acceptabel, en ook niet iedere soort resource is her-openbaar. Daarom is de dispose() generiek genoeg gemaakt om voor iedere soort resource clean-up te kunnen dienen.
Je werkt met objects, niet met resources. Als je doet:
Treenode n = myTree.Nodes.Add(...);

moet je dan n.Dispose() aanroepen als je weer doet:
myTree.Nodes.Remove(n);
?

Indien JA, dan heeft .NET een serieus probleem.
.NET heeft precies hetzelfde probleem als iedere ander platform: als je niet weet hoe alles werkt dan kun je in gigantische problemen komen. Het IDisposable patroon is juist om te voorkomen dat de finalizer wordt aangeroepen, dus Microsoft spreekt zichzelf daar echt niet tegen. Jij vindt echter IDisposable een implementatie detail dat je liever niet gebruikt. Dat ziet Microsoft dus (terecht) wel anders.
Zucht, ik word hier wel een beetje moe van. Ten eerste wil ik helemaal niet bezig zijn met resource management, daarom gebruik ik een platform met een GC. Ten tweede WEET ik helemaal niets van resource management van objects die ik gebruik, ik heb de source niet. Ten derde HOEFT het ook niet, want .NET regelt het voor me.

Waarom een interface? Ooit over nagedacht? Wellicht omdat je dan in een .NET class / onderdeel generiek die handel kunt opruimen? De eis dat je alle methods moet nagaan die geimplementeerd worden in een class en dan daarvanaf moet leiden dat je een method MOET aanroepen voor resource management lijkt me het paard achter de wagen spannen.
[...]
Zit ik er naast dan? Als jij mij betrapt op een onwaarheid in dit verhaal hoor ik het graag.
Je verhaal over dit: zodra IDisposable is geimplementeerd moet je Dispose aanroepen, of iig: de implementatie zegt dat je beter Dispose kunt aanroepen want er zit kennelijk iets intern gereferenced en dat levert anders trage software op. Wat onzin is uiteraard.
[...]
Het stukje documentatie klopt inderdaad, en beschrijft bijna letterlijk wat ik vertelde. Er staat heel duidelijk dat de finalizer van een disposable als backup moet dienen, en dat de programmeur hier zelf voor moet zorgen.
Van de class die de unsafe resource referenced ja, dus niet jouw pakkie-an.
Dat is dus net even iets minder kort door de bocht als "Wanneer een object IDisposable implementeert dan zal de GC altijd Dispose() aanroepen". De bewoordingen die gebruikt worden in dit stukje (en ik ben er zeker van dat in een andere alinea van deze help-pagina dit expliciet verteld wordt) geven ook aan dat de dispose aanroepen via de finalizer een backup, als de programmeur "gefaald" heeft.
Gefaald? Wanneer? En welke programmeur? Die programmeur die een ADO.NET connection object gebruikt in een class en niet IDisposable implementeert? Of die programmeur die een class gebruikt die IDisposable implementeert?

BEIDE hoeven IDisposable helemaal niet te implementeren. Alleen als je ZELF een unsafe object referenced. En wanneer komt dat voor? Nauwelijks. Daarnaast heb je dus het gebruik van classes die unsafe resources schijnen te gebruiken (maar dat weet jij niet en ik ook niet en ik WIL het ook helemaal niet weten). Ik ga dan echt geen Dispose() lopen aanroepen op allerlei objects of erger: hele try/finally trees bouwen alleen om ieder object dat ik creeer ook te kunnen disposen. Dat zoekt die GC maar fijn uit!

Jij doet dat wel? En wat win je ermee? 10 cycles op je P4-3Ghz? Als het werkelijk zo nodig is dan is het beter vertoeven in de COM wereld met VC++. Smartpointer naar het object en hij gaat vanzelf out-of-scope en de smartpointer ruimt het wel op. Opgelost.
[...]
Je hebt de volgende regel die ik over opgeschreven heb ook gequote, heb je hem ook gelezen? Hier legde ik uit dat de dispose methode er meestal voor zal zorgen dat de finalizer niet zal worden gebruikt in de GC, als de dispose methode al eerder is aangeroepen. Je hebt dus ALLEEN last van de performance dip als de dispose() NIET wordt aangeroepen in de code.
En hoe groot is die dip denk je... jij merkt hem echt niet hoor. Is er wel een dip die je merkt dan is het Microsoft's schuld, ZIJ zijn verantwoordelijk voor de performance van de GC, niet de developer zelf.
[...]
Sorry hoor, maar nu begin je wel een beetje kinderachtig te doen. Jij weet ook vast wel dat er zoiets als een managed heap bestaat, en die kun je net zo goed in de poeier helpen. Dat "managed" moet je wel met een korreltje zout nemen. Ik zie ook niet hoe dit nu buiten de scope van het probleem ligt. Het probleem was performance, en de performance van een applicatie kan echt dramatisch slechter zijn als je simpelweg niet goed met je objecten om gaat.
Ik doe niet kinderachtig, ik gebruik MS' marketingpoep argumenten. Als .NET developer heb je geen rekening meer te houden met memory management. Nou, dat doe ik dan ook niet. Moet ik volgens jou dan ineens daar WEL rekening mee gaan houden? Wie lult er dan uit zn nek: jij of MS? Ja ik weet ook wel dat een array van tigduizend grote objects veel memory consumption oplevert, maar de 'heap' is niets anders dan een truukje om memory allocation wat sneller te laten verlopen, iets wat hoort bij de CLR en internals niet bij de developer en zn code. Want, als het zo belangrijk is, waarom kan ik dan geen heap alloc doen of de heap grootte managen?
Dat is dus bad practice. De finalizer is een vangnet, en hier moet niet expliciet gesteund op worden door de programmeur. Dat jij vindt dat het niet zo hoeven heeft de rest van de .NET wereld eerlijk gezegd weinig aan denk ik.
Nee, JIJ vindt dat bad practise. De keypoints voor .NET zijn heel duidelijk en daar hoort eigen gepruts met memory management en vrijgeven van resources NIET bij, dat zou juist tot het verleden behoren. Overigens, wat ik vind heeft de .NET wereld wel degelijk wat aan, het lullige is dat er meer mensen naar wat ik vind luisteren dan jij denkt. Voorbeeld waarom ik gelijk heb en jij niet? Zoek de regel eens op in de .NET documentatie waarin MS expliciet zegt dat wanneer een class een public Dispose method heeft, deze ook moet worden aangeroepen?

Ik begrijp je mierenneuken ook niet echt. Is me al vaker opgevallen, maar je wilt werkelijk IEDERE regel die ik zeg en waar volgens jou iets in staat wat niet klopt rechtzetten. De DataSet bv heeft ook een Dispose() method. Ik moet het eerste stukje code nog zien waar de Dispose method van de dataset wordt aangeroepen. Jij roept bij elke dataset de Dispose() aan? Indien NEE: waarom niet? Volgens jouw woorden MOET je die aanroepen want het is een public Dispose() method.
Ik weet ook niet direct waarom een dispose() niet volgens OO zou zijn, en een close() wel.
Een Close() maakt een actie-sequence compleet. Ik kan daarna het object gewoon weer gebruiken of beter: verder gebruiken. Dispose() is ook een method net als ieder ander. Echter wanneer ik Dispose() aanroep is mn object in een toestand dat ik het niet meer kan (of jouw woorden: 'mag') gebruiken.

Dat dispose is dus een vrij rare method, want het helpt het object intern om zeep terwijl het object er nog wel gewoon is.
Een object is een eenheid van verantwoordelijkheid, en als in het contract van het object staat "roep de dispose zo snel mogelijk aan nadat je klaar met me bent, anders wordt het traag" dan zie ik niet zo goed hoe dit nu anti-OO is. Vervelend dat je er op moet letten, ben ik absoluut met je eens, maar dit is gewoon de realiteit.
Nee dat is de realiteit niet. Het is NIET traag, en er bestaan ook niet contracten als 'roep dit aan want anders!!!!'. Interface contracts gaan niet zover, zeker niet met bv C#.
[...]
Tja als je het zo bekijkt. Dat zou dus inhouden dat naast een finalizer en een dispose methode er nog een derde close() methode nodig is die alles opruimt. En is dat wel zo? Als een connectie zichzelf na een close() nog kan openen, heeft ie dan niet stiekem nog wat resources vast om dit te kunnen? Ik weet het niet, met dispose zit je altijd goed. De resources waar het bij dispose() om gaat zijn wél schaars, anders hoef je ze niet zo vroeg vrij te geven. Of stel jij het aantal connecties in je pool op oneindig in? Dan heb je niet zo veel meer aan je pool.
Huh? Weet je wel hoe een pool werkt? :? Ik weet helemaal niet welke resources een class in zich heeft en of die schaars zijn (lijkt me naar, schaarse resources. Nog een paar threads en je systeem slaat vast) en of die vrijgegeven MOETEN worden. Ik roep alleen Open aan en dan verwacht ik dat iets opent en bij Close() sluit het weer. Wat er verder onder water gebeurt zoekt die class maar fijn zelf uit. Ga jij jezelf vermoeien met hoe dat dan allemaal intern gebeurt en wat 10 cycles bespaart? Jij roept ipv Close() gewoon Dispose() aan op een ado.net connection, omdat Dispose() onder water close() aanroept?
[...]
Heel simpel: als ie disposable is, dan moet je er altijd voor zorgen dat de dispose() wordt aangeroepen.
Goed, dus jij roept Dispose() aan op dataset, datatable, dataview? En op al je controls natuurlijk wanneer je een form sluit! Want een grid bv die een dataset bind bind met een dataview. Dus je moet dan die dataview OOK disposen wanneer de grid out of scope gaat. Wat een overbodige code. Het is nl. nergens voor nodig.
Jij roept "maar 2 gig aan handles", nou als een applicatie dat ook daadwerkelijk alloceert dan merk je wel degelijk dat dat niet zo handig is. Je hoeft niet precies te weten wat er allemaal onder water gebeurt, je hoeft alleen te weten dat je netjes disposed, en dan zal het allemaal in de meeste gevallen wel loslopen.
Wat is dat nou voor redenatie.. "roep maar aan, baat het niet dan schaadt het ook niet, en het helpt... meestal". Als mn applicatie uit zn resources loopt (en dat lijkt me heel sterk, met 2gb aan handles per process) dan heb ik een probleem. Ik kan dan disposen wat ik wil, maar dat boeit niet, ik zal toch aan die limiet raken bij iets meer load bijvoorbeeld.

Als je bv gewoon Close() aanroept op streams, op Connections e.d. dan heb je het al opgelost. Dat dispose aanroepen op treenodes bv is zo ongelovelijke onzin. Een form heeft wellicht honderden HWND's en per control (en subcontrol) een HDC... worden die wel allemaal op tijd gedisposed, anders krijg je heap trashing!. Nee dus. .NET is juist ervoor om je NIET meer zorgen te maken over dat soort shit. Jij mag je hoofd daar middagen over breken hoor, maar je hebt alleen jezelf er maar mee.
[...]
Tja event handlers zijn natuurlijk weer een totaal andere discussie. En als jij het zo vervelend vind om dispose te doen dat je alles vertraagt door het de finalizers op te laten knappen, be my guest.
HOEveel vertraagt het dan? Weet je dat uberhaupt wel? En als ik geen unmanaged resources reference (indirect) door die objects, of het dan uberhaupt wel traag is? (nee)

Jij bent o zo bang voor dangling resources. Het zijn juist dangling eventhandlers die voor referenced objects zorgen die niet meer gefreeed worden en DUS voor memory leaks en dus tragere performance. Niet die dispose calls.
Als je maar even een dikke disclaimer bij zet: Dit is omdat EfBe dit mooier vindt, maar is géén practice die Microsoft aanbeveelt en Het is géén onzin als je toch zelf de dispose aanroept, want dat is wel sneller.. ;) Dit is wel een openbaar forum, dus ik vind dat je de verantwoordelijkheid hebt om dingen te vertellen zoals ze zijn, niet zoals je ze graag zou willen hebben.
Zeg piet snot, jij vertelt mij echt niet wat ik moet en niet moet, dus beetje dimmen. :/ Ik vertel hoe het is, jij maakt mensen bang met prietpraatjes over traagheid bij het niet doen van bepaalde dingen. MS beveelt helemaal NIETS aan, nergens staat dat je Dispose moet aanroepen a.s.a.p. en wanneer je kunt en bij ieder object die Dispose implementeert.

Maar ik heb nu wel genoeg tijd verknoeid aan deze onzin discussie.

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
Weet je EfBe ik reageer hier niet eens meer op. Hopelijk lees je over een week dit nog ff na en denk je er dan anders over, misschien heb je dan nog het fatsoen om je excuses te maken. Ik heb al aangegeven dat het .NET framework in veel gevallen zelf de dispose aanroept, en dan hoef je het zelf dus niet te doen. Tuurlijk zal ook niet meteen de hemel naar beneden vallen als het niet altijd gebeurt. Maar je hoort het wél in de gaten te houden. Denk jij dat finalizers geen performance probleem kunnen betekenen, en als dat wel zo zijn dan was het de schuld van Microsoft? Tja zo kan ik ook een discussie proberen te winnen. Ik bouw er alleen geen snelle programma's mee.

Ik geef verder ook alleen maar aan wat de best practice vanuit microsoft is. Als jij het daar niet eens bent vind ik dat prima. Maar durf dat gewoon te zeggen. Ik verkoop geen onzin en dat weet je best. Je hoeft dus ook niet te doen alsof ik dat wel zou doen.

Ik vind deze kromme discussie ook niet passen met een MVP, jullie zouden dit soort dingen toch ook moeten weten. We hebben er al een paar eerder gehad, en dit lijkt mij eerder te gaan om het feit dat je niet zo graag tegengesproken wordt, want ik zie er nogal vreemde (tegen)argumenten, die eerder als (wanhoops)poging dienen om mijn verhaal onderuit te halen dan een zinnig alternatief.

Het is niet erg om een verschillende mening te hebben, zo lang de argumenten maar kloppen. Argumenten die kloppen daar moet je respect voor hebben, en niet als onzin of implementatie detail van tafel vegen. Dat deze discussie weer hopeloos offtopic zou gaan was ik ook wel bang voor. De volgende keer zal ik bill gates je wel even laten bellen om wat dingen bij te praten, ipv via got een nieuw moddergevecht te starten. ;)

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 12-05 22:23

curry684

left part of the evil twins

Heren, houdt het aub wederzijds netjes :) Dat je het niet met elkaar eens bent kan gebeuren, maar bespreek die discrepantie dan wel met een stukje wederzijds respect ;)

[ Voor 57% gewijzigd door curry684 op 27-09-2004 10:48 . Reden: foutje :) ]

Professionele website nodig?


  • EfBe
  • Registratie: Januari 2000
  • Niet online
misfire schreef op 27 september 2004 @ 07:53:
Weet je EfBe ik reageer hier niet eens meer op. Hopelijk lees je over een week dit nog ff na en denk je er dan anders over, misschien heb je dan nog het fatsoen om je excuses te maken.
Als ik wat fout doe dan bied ik mijn excuses daarvoor wel aan hoor. Ik zie echter niet 1 2 3 in wat ik fout heb gedaan. Volgens mij begon het dispuut over die dispose calls in die tree die volgens jou wel en volgens mij niet nuttig waren. Waarom dan ik excuses moet aanbieden is me een raadsel. :)
Ik heb al aangegeven dat het .NET framework in veel gevallen zelf de dispose aanroept, en dan hoef je het zelf dus niet te doen. Tuurlijk zal ook niet meteen de hemel naar beneden vallen als het niet altijd gebeurt. Maar je hoort het wél in de gaten te houden.
Ik wil met alle liefde met je mee gaan in deze gedachtegang, maar ik kan geen bewijs vinden dat ik dat behoor te doen, dat in de gaten houden. Snap je nu wat ik bedoel?
Denk jij dat finalizers geen performance probleem kunnen betekenen, en als dat wel zo zijn dan was het de schuld van Microsoft? Tja zo kan ik ook een discussie proberen te winnen. Ik bouw er alleen geen snelle programma's mee.
ten eerste zijn finalizers niet erg gebruikelijk omdat je niet vaak met unsafe resources te maken hebt maar met FCL classes. ten tweede is de performance dip niet aangetoond in benchmarks, alleen in sommige presentaties van bepaalde MS medewerkers. Er zal ongetwijfeld een paar cycles aan opgaan aan die finalizer, maar vergeet niet dat het niet mijn probleem is als dat traag is. Ik verval in herhalingen, maar als ik in .NET net zoveel overhead moet creeeren voor een efficient programma als in COM met C++ dan kies ik echt voor het laatste want ik weet dat dan mijn programma sneller is dan de .NET equivalent. M.a.w.: Microsoft heeft deze GC constructie bedacht en is dus de oorzaak van een eventuele screw-up in het design. Vind ik verder niet zo belangrijk, die paar cycles boeien mij eerlijk gezegd niet zo, want als performance een hot issue is moet je geen .NET kiezen.
Ik geef verder ook alleen maar aan wat de best practice vanuit microsoft is. Als jij het daar niet eens bent vind ik dat prima. Maar durf dat gewoon te zeggen. Ik verkoop geen onzin en dat weet je best. Je hoeft dus ook niet te doen alsof ik dat wel zou doen.
Ik zou je graag willen geloven, maar ik kan geen bewijs vinden waar Microsoft deze 'best practise' duidelijk uitlegt. De reden waarom ik hier zo over doorzeik is de volgende: de meeste .NET ontwikkelaars gebruiken visual studio.net, en leren het gebruik van .NET tijdens een project, na een cursusje basisbegrippen en / of een boek. In de MSDN is geen 'best practises' sectie opgenomen voor .NET met een artikel over het gebruik van Dispose zoals jij dat propageert. Jij kunt dan wel beweren dat Microsoft het als 'best practise' verkondigt maar dat bereikt dan verdomd weinig mensen, die Microsoft 'best practise' campagne. Verder, als je een 'best practise' NODIG hebt voor efficiente programmatuur, is er iets mis met je design, immers de reguliere intuitieve manier van gebruiken leidt tot niet-efficiente programmatuur.
Ik vind deze kromme discussie ook niet passen met een MVP, jullie zouden dit soort dingen toch ook moeten weten. We hebben er al een paar eerder gehad, en dit lijkt mij eerder te gaan om het feit dat je niet zo graag tegengesproken wordt, want ik zie er nogal vreemde (tegen)argumenten, die eerder als (wanhoops)poging dienen om mijn verhaal onderuit te halen dan een zinnig alternatief.
Mja, hoe jij tegen de wereld aankijkt moet je natuurlijk zelf weten. Wat je wellicht niet weet is dat ik bijna dagelijks (en met mij veel MVP's overigens) een karrevracht aan gezeik over me heen krijg omdat mensen ergens niet eens zijn met een detail in een redenering van wat ik zeg/beweer. Veelal is dit puur getriggered omdat ze de titel 'MVP' zien staan en dat ze deze selfproclaimed 'guru' wel even zullen vertellen dattie fout zit!

In het begin word je daar niet vrolijk van, maar na een tijdje begin je een patroon te herkennen, hoort van andere MVP's dat ze hetzelfde ervaren en sommigen zijn daardoor bv veel minder gaan posten in newsgroups e.d. Het punt is, misfire, ik hoef mij echt niet meer te bewijzen dat ik weet waarover ik praat. Als ik ergens een detail mis, soit, ik ben geen robot. Als jij mij zo nodig de les moet lezen over Dispose(), iets onbenulligs in de context van waar het ter sprake kwam, dan moet je dat maar doen. Wat je ermee wilt bereiken is mij wel duidelijk, en wellicht is je dat gelukt ook, dat kan me echt niet zo boeien eerlijk gezegd. Wat me wel stoort is dat je je het schompes claimt maar niet vragen daarover beantwoordt, zoals of jij bij elke dataset de dispose() aanroept, want dat beweer je zelf dat je dat moet doen. Simpele dingen die mij vertellen waarom deze discussie (net als de vorige) is ontstaan.
Het is niet erg om een verschillende mening te hebben, zo lang de argumenten maar kloppen. Argumenten die kloppen daar moet je respect voor hebben, en niet als onzin of implementatie detail van tafel vegen.
Ik respecteer diegene die iets exceptioneels gedaan heeft. Iemand die argumenten oplepelt doet gewoon zn ding in een discussie. Als ik iets doe wat jou niet zindt bv is het jouw goed recht dat als onzin te betitelen net zoals het mijn goed recht is om jouw beweegredenen te bekritiseren of jouw argumentatie onderuit te halen.
Dat deze discussie weer hopeloos offtopic zou gaan was ik ook wel bang voor. De volgende keer zal ik bill gates je wel even laten bellen om wat dingen bij te praten, ipv via got een nieuw moddergevecht te starten. ;)
haha... :/ Ik denk niet dat er een volgende keer komt, misfire. De volgende keer dat jij reageert op mijn postings doe je dat voor jezelf, want een reactie krijg je niet.

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com

Pagina: 1