Benutzer mit den meisten Antworten
frage zu kündigung von ereignissen bei lambda / anonymen delegates : memory-leak

Frage
-
hallo...
ich habe mal ne frage, ob man so ein memory-leak problem bekommt, wenn man sich per lambda oder anonymen delegate an ein ergeignis anhängt...private void button_Click(object sender, RoutedEventArgs e) { foo(); } private void foo() { MyFooWebServiceClient proxy = new MyFooWebServiceClient(); proxy.FooCompleted += (sender, e) => { // ... }; proxy.FooAsnyc(); }
...also wenn ein button_Click ausgelöst wird, rufe ich in der methode foo() einen webservice auf, an dessen FooComplete ereignis ich mich über die lambda schreibweise (anonymer delegate) anhänge / abonniere und über FooAsync() die webservice anfrage starte.
frage...
wird das erzeugte MyFooWebServiceClient objekt vom GarbageCollector gelöscht oder nicht...?
denn es heißt ja, solange einer als abonnent an einem objekt hängt, wird das objekt nicht vom GC gelöscht.
ich generiere ja bei jedem aufruf von foo() ein neues MyFooWebServiceClient objekt und hänge ein anonyme delegate als abonent an FooCompleted... aber nirgend wo kündige ich mein abonnement am FooCompleted ereignis.wenn ich ein memory-leak verursache, dann die frage, wie kündige ich in diesen fall mein abo an FooCompleted, so dass der GC das objekt auch wirklich löschen kann...
diese lambda schreibweise sehe ich ja öffters... und niemand scheint sich gedanken über -= zu machen.
PS.: das beispiel ist aus einen Silverlight projekt, da gibt es nur die FooAsync / FooCompleted möglichkeit, um webservices aufzurufen. (nur falls jemand auf die idee kommt mich zu fragen, warum ich denn nicht proxy.Foo() aufrufe... außerdem ist es ja eine generelle frage)
Antworten
-
Hallo Axel,
Der Lambda-Block verursacht eine sog. Closure, d.h. es bindet an die Variablen von foo, darunter auch an proxy. Solange Deine Form geöffnet ist, wird proxy nicht finalisiert werden, wenn die Form erreichbare Referenzen zu den proxy-Objekten hält (die ihrerseit eine Referenz zum jeweiligen Handler halten). Das kannst Du ganz einfach feststellen, indem Du ein Ereignis in einer benutzerdefinierten Klasse implementierst und dort einen Destruktor einsetzt, der Dir die Finalisierung meldet. Sobald die Form geschlossen wird, werden die proxy-Destruktoren aufgerufen.
Ian Griffith: Event Handlers, Circular References, and Alleged Memory Leaks
http://www.interact-sw.co.uk/iangblog/2004/07/07/circulareventrefsLösungsansätze (mit Vor- und Nachteilen):
Daniel Grunwald: Weak Events in C#
http://www.codeproject.com/KB/cs/WeakEvents.aspxGruß
Marcel- Als Antwort markiert axel-at-iw Freitag, 27. August 2010 12:50
-
Hallo Axel,
Nein, Du machst keinen Denkfehler. Sofern proxy nicht weiter referenziert wird, wird das nicht weiter erreichbare Objekt früher oder später vom GC eingesammelt auch ohne GC.Collect() bzw. GC.WaitForPendingFinalizers().
Gruß
Marcel- Als Antwort markiert axel-at-iw Freitag, 27. August 2010 12:59
-
Hallo Axel,
ggf. eine Anrede, oder ein Quoting (Zitieren) des Textes wäre vorteilhaft.
> das mach ich eben nicht, wenn ich bei jedem aufruf einen
> WebServiceClientProxy erstelle. zu im konstruktor hab ich
> das objekt ja noch garnicht...
> oder kann ich den WebServiceClientProxy einmal erstellen und
> dann beliebig oft verwenden (insbesondere mit konkurierenden threads
> - ohne dass es knallt)?ja - nur einmal instanziieren, denn man ruft ja in SL immer die ***Async - Methoden auf. Das Service-Objekt braucht dann nur einmal im Konstruktor erstellt werden. Ist dann auch performanter.
[Vorgehensweise: Zugreifen auf einen Dienst über Silverlight]
http://msdn.microsoft.com/de-de/library/cc197937(VS.95).aspx
Es steht Dir trotzdem frei, immer wieder (bei jedem Proxy-Aufruf) einen neuen Proxy zu erstellen. Ein MemoryLeak entsteht dann nicht, wenn Du sauber Close ausführst, aber man muss dann (je nach WCF Szenario) gut auf das Fehler-Handling aufpassen. (z.B. Abort bei Exception ausführen etc.).
Jetzt sind wir aber etwas abgekommen - die Frage nach "-=" Möglichkeiten bei Lambdas ist ja schon in meinem ersten Posting von mir beantwortet worden.
ciao Frank- Als Antwort markiert axel-at-iw Freitag, 27. August 2010 12:59
Alle Antworten
-
Hallo Axel,
Der Lambda-Block verursacht eine sog. Closure, d.h. es bindet an die Variablen von foo, darunter auch an proxy. Solange Deine Form geöffnet ist, wird proxy nicht finalisiert werden, wenn die Form erreichbare Referenzen zu den proxy-Objekten hält (die ihrerseit eine Referenz zum jeweiligen Handler halten). Das kannst Du ganz einfach feststellen, indem Du ein Ereignis in einer benutzerdefinierten Klasse implementierst und dort einen Destruktor einsetzt, der Dir die Finalisierung meldet. Sobald die Form geschlossen wird, werden die proxy-Destruktoren aufgerufen.
Ian Griffith: Event Handlers, Circular References, and Alleged Memory Leaks
http://www.interact-sw.co.uk/iangblog/2004/07/07/circulareventrefsLösungsansätze (mit Vor- und Nachteilen):
Daniel Grunwald: Weak Events in C#
http://www.codeproject.com/KB/cs/WeakEvents.aspxGruß
Marcel- Als Antwort markiert axel-at-iw Freitag, 27. August 2010 12:50
-
Hallo Axel,
Du könntest den Typ des Lambda-Ausdruckes bekannt machen:var dd = (EventHandler<FooCompletedEventArgs>) ((s, e) => { MessageBox.Show("Foo beendet"); }); data.FooCompleted += dd; // rückgängig: data.FooCompleted -= dd;
Ansonsten sind Lamdas hier ja zum einem nur eine Vereinfachung der Schreibweise gedacht, die man aber in solchen Fällen ja auch durch explizite Methoden ersetzen kann.
Und - jetzt mal auf Deinen SL-Service bezogen - .. da würdest Du ja eher die Completed-Ereignisse einmalig (vielleicht im Konstruktor) abbonieren - sodass ein -= nicht benötigt wird.
ciao Frank -
Hallo Marcel...
ich hab jetzt einfach mal selber in silverlight einen test gestrickt (vielleicht bissel umständlich)... einfach zwei buttons bei denen einmal die foo(...) aufgerufen wird, und zum anderen der GC angestoßen wird... dann zähle ich einfach, wenn ich ein proxy erstelle +1, und wenn eines zerstört wird -1...public partial class FooTest : UserControl { public FooTest() { InitializeComponent(); } private void foo(object obj) { FooClass proxy = new FooClass(); proxy.FooCompleted += (sender, e) => { Debug.WriteLine("foo.FooCompleted {0}", obj); }; proxy.FooAsnyc(obj); } private int i = 0; private void buttonTriggerFoo_Click(object sender, System.Windows.RoutedEventArgs e) { foo(++i); } private void buttonTriggerGarbageCollector_Click(object sender, System.Windows.RoutedEventArgs e) { GC.Collect(); GC.WaitForPendingFinalizers(); } public static int FooClassCounter = 0; public static int FooArgsCounter = 0; } public class FooClass { public FooClass() { Debug.WriteLine("FooClass() [{0}] ", Interlocked.Increment(ref FooTest.FooClassCounter)); } ~FooClass() { Debug.WriteLine("~FooClass [{0}]", Interlocked.Decrement(ref FooTest.FooClassCounter)); } public void FooAsnyc(object data) { Debug.WriteLine("FooClass.FooAsnyc({0})", data); BackgroundWorker backgroundWorker = new BackgroundWorker(); backgroundWorker.DoWork += (sender, e) => { Debug.WriteLine("FooClass_DoWork({0})", e.Argument); Thread.Sleep(2000); e.Result = e.Argument; }; backgroundWorker.RunWorkerCompleted += (sender, e) => { Debug.WriteLine("FooClass_RunWorkerCompleted({0})", e.Result); EventHandler<FooCompletedArgs> handler = FooCompleted; if (handler != null) handler(this, new FooCompletedArgs(e.Result)); }; backgroundWorker.RunWorkerAsync(data); } public event EventHandler<FooCompletedArgs> FooCompleted; } public class FooCompletedArgs : EventArgs { public object Data { get; private set; } public FooCompletedArgs(object data) { Debug.WriteLine("FooCompletedArgs({0}) [{1}] ", data, Interlocked.Increment(ref FooTest.FooArgsCounter)); Data = data; } ~FooCompletedArgs() { Debug.WriteLine("~FooCompletedArgs [{0}]", Interlocked.Decrement(ref FooTest.FooArgsCounter)); } }
mit dem ergebnis, die proxy objekte werden alle beim aufruf des GC gelöscht... zumindest werden soviele destruktoren aufgerufen, wie konstruktoren aufgerufen wurden - das sollten dann doch alle sein... oder?
(hab ca.100 mal proxys erstellt und nach GC ist beim letzten der zähler wieder auf null gewesen)hauptsache der GC triee auch mal selber in aktion wenn er es für nötig hält... :)
also könnte ich diese lambda schreibweise enutzen, ohne speicher zu belegen, wer nicht wieder freigegeben wird...
oder mach ich da doch nen denkfehler...? -
das mach ich eben nicht, wenn ich bei jedem aufruf einen WebServiceClientProxy erstelle. zu im konstruktor hab ich das objekt ja noch garnicht...
oder kann ich den WebServiceClientProxy einmal erstellen und dann beliebig oft verwenden (insbesondere mit konkurierenden threads - ohne dass es knallt)? -
Hallo Axel,
Nein, Du machst keinen Denkfehler. Sofern proxy nicht weiter referenziert wird, wird das nicht weiter erreichbare Objekt früher oder später vom GC eingesammelt auch ohne GC.Collect() bzw. GC.WaitForPendingFinalizers().
Gruß
Marcel- Als Antwort markiert axel-at-iw Freitag, 27. August 2010 12:59
-
Hallo Axel,
ggf. eine Anrede, oder ein Quoting (Zitieren) des Textes wäre vorteilhaft.
> das mach ich eben nicht, wenn ich bei jedem aufruf einen
> WebServiceClientProxy erstelle. zu im konstruktor hab ich
> das objekt ja noch garnicht...
> oder kann ich den WebServiceClientProxy einmal erstellen und
> dann beliebig oft verwenden (insbesondere mit konkurierenden threads
> - ohne dass es knallt)?ja - nur einmal instanziieren, denn man ruft ja in SL immer die ***Async - Methoden auf. Das Service-Objekt braucht dann nur einmal im Konstruktor erstellt werden. Ist dann auch performanter.
[Vorgehensweise: Zugreifen auf einen Dienst über Silverlight]
http://msdn.microsoft.com/de-de/library/cc197937(VS.95).aspx
Es steht Dir trotzdem frei, immer wieder (bei jedem Proxy-Aufruf) einen neuen Proxy zu erstellen. Ein MemoryLeak entsteht dann nicht, wenn Du sauber Close ausführst, aber man muss dann (je nach WCF Szenario) gut auf das Fehler-Handling aufpassen. (z.B. Abort bei Exception ausführen etc.).
Jetzt sind wir aber etwas abgekommen - die Frage nach "-=" Möglichkeiten bei Lambdas ist ja schon in meinem ersten Posting von mir beantwortet worden.
ciao Frank- Als Antwort markiert axel-at-iw Freitag, 27. August 2010 12:59