Laden...

Abmelden von EventHandlern in einem komplex Vernetzten Program

Erstellt von Myrkjartan vor 3 Jahren Letzter Beitrag vor 3 Jahren 1.556 Views
M
Myrkjartan Themenstarter:in
6 Beiträge seit 2020
vor 3 Jahren
Abmelden von EventHandlern in einem komplex Vernetzten Program

Hallo Comunity,
ich arbeite an einem sehr komplexen Program, das EventHandler benutzt um zwischen Klassen zu komunizieren. Vor mir waren schon dutzende von Programmieren an diesem Projekt zu Gange. Mir ist an einem Punkt aufgefallen, dass es sehr weitreichende MemoryLeaks gibt, was ich erst einmal darauf zurück geführt habe, dass Eventhandler miteinander Verknüpft werden und diee verknüpfungen dafür sorgen, dass der GC die Elemente mit diesen EventHandlern nicht wegräumt, da es noch Verweise gibt.
Da es aufgrund der Programstruktur kompliziert bis unmöglich ist die EventHandler zu deregistrieren, habe ich folgende Idee gehabt:
Ich habe eine Etension Methode geschrieben, die alle registrierten EventHandler entfernen soll. Diese sieht so aus:

public static void ClearAllRegisteredHandler(this EventHandler handler)
        {
            foreach (EventHandler eh in handler?.GetInvocationList() ?? new Delegate[0])
            {
                handler -= eh;
            }
        }

Da sich mein Verständnis für die inneren Zusammenhänge von EventHandlern doch eher in Grenzen hält, ist meine Frage schlicht diese:
Funktioniert das?

2.078 Beiträge seit 2012
vor 3 Jahren

Bist Du dir sicher, was Du da tust?
Das schreit nach Fehlern, einfach alle EventHandler zu entfernen.

Soweit ich weiß, geht das nur umständlich per Reflection.

Die einfachste variante ist denke ich, dass Du den add- bzw. remove-Accessor vom Event veränderst und dann die EventHandler einer Liste hinzufügst:

public event EventHandler MyEvent
{
    get => _myEventHandlers.Add(value);
    remove => _myEventHandlers.Remove(value);
}

Das Ausführen der EventHandler musst Du dann allerdings auch manuell machen, indem Du die Liste durchläufst und alle einzeln ausführst.
Ist ziemlich überflüssig und fast immer eine blöde Idee, aber dafür kannst Du sehr simpel alle EventHandler entfernen, indem Du einfach die Liste leerst.

M
Myrkjartan Themenstarter:in
6 Beiträge seit 2020
vor 3 Jahren

Ich bin mir nicht sicher was ich da mache....
Es ist aber so gedacht, dass immer alle EventHandler entfernt werden. Ich benutze diese Funktion nur dann, wenn ich das Fenster schließe, an welcher Stelle alle entfernt werden müssen.

2.078 Beiträge seit 2012
vor 3 Jahren

Naja, kommt drauf an, um welche Events es da geht.

Wenn Du das Fenster schließt und das Objekt dann auch verloren gehen darf, werden auch alle EventHandler zu den Events dieses Fensters verworfen.
Außer es wurden irgendwo welche gespeichert, aber davon gehe ich Mal nicht aus.

Wenn es um die Events von anderen Klassen geht und "im" Fenster wurden EventHandler registriert, dann wird das schon spannender, denn Du kannst die EventHandler nicht unterscheiden. Wenn Du alle Handler entfernst sind wirklich alle weg, nicht nur die, die vom Fenster registriert wurden.

Ich vermute Mal, irgendwelche Fenster-Methoden dienen als EventHandler von anderen Business-Klassen? In dem Fall wäre es das klügste, dass das Fenster alle diese EventHandler beim Schließen wieder entfernt. Das ist der einzige Weg, wie Du erreichen kannst, dass nur die EventHandler verschwinden, die auch weg können.

M
Myrkjartan Themenstarter:in
6 Beiträge seit 2020
vor 3 Jahren

Also, wenn ich deine Vermutung richtig verstanden habe, dann ist es genau so.
Ich versuchwe mal, das verständlich zu beschreiben:
Klasse A (wpf) hat einen EventHandler X.
Klasse B,C,D und E benutzen Klasse A und registrieren eigene EventHandler auf X. Diese sind teils tatschlich deklarierte EventHandler, teils auch nur Lambda Ausdrücke.
Wenn das Fenster geschlossen wird, müssen alle auf X registrierten EventHandler gelöscht werden, da sonst Verweiße auf diese vorhanden sind, weshalb der GC A nicht wegräumt und dieses Element im Speicher bleibt bis das Program komplett geschlossen wird.
Deshalb war mein Plan dann beim Schließen A.ClearAllRegisteredHandler() auszuführen, was meines Verständnisses nach, genau das machst was du vorgeschlagen hast.

In dem Fall wäre es das klügste, dass das Fenster alle diese EventHandler beim Schließen wieder entfernt

Habe ich das richtig Verstanden?

2.078 Beiträge seit 2012
vor 3 Jahren

Wenn ich dich jetzt wiederum richtig verstanden habe, ist das der Fall, wo es tatsächlich funktionieren könnte.

Ergo:

private readonly ICollection<EventHandler> _myEventHandlers;

public event EventHandler MyEvent
{
    get => _myEventHandlers.Add(value);
    remove => _myEventHandlers.Remove(value);
}

private void ClearMyEvent()
{
    _myEventHandlers.Clear();
}
private void InvokeMyEvent(EventArgs e)
{
    foreach (var handler in _myEventHandlers)
        handler(this, e);
}
M
Myrkjartan Themenstarter:in
6 Beiträge seit 2020
vor 3 Jahren

OK, dann bedanke ich mich erst einmal für die Hilfe und werde mich zunächst durch die 10.000 Klassen arbeiten die das Programm enthält und die MemoryLeaks auf diese Art stopfen. Mal sehen ob's hilft.

2.078 Beiträge seit 2012
vor 3 Jahren

Ach warte - das sind vermutlich vordefinierte Events?

private void Clear()
{
    var myEventInfo = typeof(Program).GetEvent(nameof(MyEvent));

    foreach (var handlerDelegate in MyEvent.GetInvocationList())
    {
        myEventInfo.RemoveEventHandler(this, handlerDelegate);
    }
}

Grausig, aber bitte

PS:
Man könnte es in eine eigene Klasse ausgliedern, indem man als Parameter eine LambdaExpression entgegen nimmt:

ClearEvent(() => MyEvent);

public static void ClearEvent(Expression<Func<MulticastDelegate>> getEventExpression)
{
    var (eventInfo, owner) = AnalyzeEventInfoExpression(getEventExpression);
    var eventMulticastDelegate = getEventExpression.Compile()();

    foreach (var handlerDelegate in eventMulticastDelegate.GetInvocationList())
    {
        eventInfo.RemoveEventHandler(owner, handlerDelegate);
    }
}
private static (EventInfo eventInfo, object owner) AnalyzeEventInfoExpression(Expression<Func<MulticastDelegate>> getEventExpression)
{
    // ...
}

Das geht - man kann anhand der LambdaExpression die Instanz mit dem Event und die EventInfo dazu herausfinden.

F
10.010 Beiträge seit 2004
vor 3 Jahren

Bei der größe des Progams, würde ich als aller erstes einen Memoryprofiler nehmen und schauen, was tatsächlich so los ist.

Bei einem Program das ich mal in die Finger bekommen habe, war es das falsche DB handling.
Die SW hat ihre Commands nie disposed und das hat dann irgendwann zu Problemen geführt.

M
Myrkjartan Themenstarter:in
6 Beiträge seit 2020
vor 3 Jahren

Ich bin bereits mit einem MemoryProfiler drüber gegangen, was mich am Ende auf die EventHandler gebracht hat.
Was meinst du mit 'vordefinierte Events' ?

2.078 Beiträge seit 2012
vor 3 Jahren

Damit meine ich die Events, die Du nicht bearbeiten kannst, also z.B. Click-Events.

Und ich hab oben noch ein Konzept ergänzt, wie man die Reflection-Varianta ausgliedern kann.
Ist nicht einfach, aber soweit ich mich erinnere, geht das.

2.078 Beiträge seit 2012
vor 3 Jahren

Ich hatte Langeweile:

private static (EventInfo eventInfo, object owner) AnalyzeEventInfoExpression(Expression<Func<MulticastDelegate>> getEventExpression)
{
    if (getEventExpression.Body is MemberExpression bodyExp)
    {
        object owner;

        if (bodyExp.Expression is ConstantExpression thisExp)
        {
            owner = thisExp.Value;
        }
        else if (bodyExp.Expression is MemberExpression varExp && varExp.Expression is ConstantExpression varOwnerExp)
        {
            owner = (varExp.Member as FieldInfo).GetValue(varOwnerExp.Value);
        }
        else
            throw new Exception("Wrong lambda expression");

        var eventFieldInfo = bodyExp.Member;
        var eventInfo = eventFieldInfo.DeclaringType.GetEvent(eventFieldInfo.Name);

        return (eventInfo, owner);
    }

    throw new Exception("Wrong lambda expression");
}

Aus der Instanz aufrufen: ClearEvent(() => MyEvent);
Mit einer Variable aufrufen: ClearEvent(() => myVar.MyEvent);

Aber mach im Code deutlich, dass das absolut der falsche Weg ist.
Ich seh's sonst noch kommen, dass die Methode überall genutzt wird, weil es eben einfacher ist.

3.170 Beiträge seit 2006
vor 3 Jahren

Hallo,

eine wichtige Anmerkung zu der Extension-Methode im Startbeitrag, da hier offenbar ein massives Verständnisproblem bezüglich Delegates/Events vorliegt: Diese Methode bringt gar nichts!
Sobald ein EventHandler einer Variable zugewiesen oder als Parameter übergeben wird, arbeitest Du nur noch auf einer **Kopie **des EventHandlers.
Aus dieser Kopie werden zwar mit der Extensionmethode alle Handler entfernt, das interssiert aber den ursprünglich an die Methode übergebenen EventHandler nicht die Bohne - da bleibt alles weiter registriert.

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

W
955 Beiträge seit 2010
vor 3 Jahren

Du könntest dich auch mal in das Thema weak events einlesen.

M
Myrkjartan Themenstarter:in
6 Beiträge seit 2020
vor 3 Jahren

@MarsStein
Das habe ich tatsächlich nicht gewusst. Wenn ich das ganze so abändere, dass die (dann leere) Kopie des EventHandlers zurückgegeben wird, würde das funktionieren?