4 Stimmen

Finalizer greifen auf verwaltete Daten zu

Ich bin mir bewusst, dass Finalizer in der Regel verwendet werden, um nicht verwaltete Ressourcen zu kontrollieren. Unter welchen Umständen kann ein Finalizer mit verwalteten Ressourcen umgehen?

Nach meinem Verständnis verhindert das Vorhandensein in der Finalizer-Warteschlange, dass ein Objekt oder Objekte, auf die stark verwiesen wird, abgeholt werden, aber es schützt sie (natürlich) nicht vor der Finalisierung. Sobald ein Objekt abgeschlossen ist, wird es aus der Warteschlange entfernt, und alle Objekte, auf die es verweist, sind beim nächsten GC-Durchlauf nicht mehr vor der Abholung geschützt. Wenn ein Finalizer aufgerufen wird, können die Finalizer für jede beliebige Kombination von Objekten, auf die das Objekt verweist, aufgerufen worden sein; man kann sich nicht darauf verlassen, dass die Finalizer in einer bestimmten Reihenfolge aufgerufen werden, aber die Objektreferenzen, die man besitzt, sollten noch gültig sein.

Es ist ziemlich klar, dass ein Finalizer niemals Sperren erwerben oder versuchen darf, ein neues Objekt zu erstellen. Nehmen wir jedoch an, dass ich ein Objekt habe, das einige Ereignisse abonniert, und ein anderes Objekt, das die Ereignisse tatsächlich verwendet. Wenn das letztere Objekt für die Garbage Collection in Frage kommt, möchte ich, dass sich das erstere Objekt so schnell wie möglich von den Ereignissen abmeldet. Beachten Sie, dass das erstgenannte Objekt erst dann für die Finalisierung in Frage kommt, wenn es von keinem lebenden Objekt mehr abonniert wird.

Wäre es praktisch, einen sperrfreien verknüpften Listenstapel oder eine Warteschlange von Objekten zu haben, die abgemeldet werden müssen, und den Finalizer des Hauptobjekts einen Verweis auf das andere Objekt auf den Stapel/die Warteschlange legen zu lassen? Das Objekt der verknüpften Liste müsste bei der Erstellung des Hauptobjekts zugewiesen werden (da die Zuweisung innerhalb des Finalisierers verboten wäre), und es wäre wahrscheinlich notwendig, etwas wie ein Timer-Ereignis zu verwenden, um die Warteschlange abzufragen (da die Abmeldung des Ereignisses außerhalb des Finalisierer-Threads erfolgen müsste), und es wäre wahrscheinlich albern, einen Thread zu haben, dessen einziger Zweck es ist, darauf zu warten, dass etwas in der Finalizer-Warteschlange erscheint), aber wenn der Finalizer sicher auf sein zuvor zugewiesenes Linked-List-Objekt und das mit seiner Klasse verbundene Haupt-Warteschlangenobjekt verweisen könnte, könnte er es ermöglichen, dass die Ereignisse innerhalb von 15 Sekunden oder so nach der Finalisierung abbestellt werden.

Wäre das eine gute Idee? (Anmerkungen: Ich verwende .net 2.0; außerdem könnte ein Versuch, dem Stapel oder der Warteschlange etwas hinzuzufügen, ein paar Mal auf Threading.Interlocked.CompareExchange drehen, aber ich würde nicht erwarten, dass es jemals sehr lange stecken bleiben sollte).

EDIT

Natürlich sollte jeder Code, der Ereignisse abonniert, iDisposable implementieren, aber Einwegartikel werden nicht immer richtig entsorgt. Wenn es so wäre, gäbe es keine Notwendigkeit für Finalizer.

Mein Szenario wäre etwa folgendes: eine Klasse, die iEnumerator(of T) implementiert, hängt sich an ein changeNotify-Ereignis ihrer zugehörigen Klasse, so dass eine Aufzählung sinnvoll behandelt werden kann, wenn sich die zugrundeliegende Klasse ändert (ja, ich weiß, dass Microsoft der Meinung ist, dass alle Enumeratoren einfach aufgeben sollten, aber manchmal ist ein Enumerator, der weiter funktionieren kann, nützlicher). Es ist durchaus möglich, dass eine Instanz der Klasse im Laufe von Tagen oder Wochen viele tausend oder sogar Millionen Mal aufgezählt wird, aber in dieser Zeit überhaupt nicht aktualisiert wird.

Im Idealfall würde der Enumerator nie vergessen werden, ohne dass er entsorgt wird, aber Enumeratoren werden manchmal in Kontexten verwendet, in denen "foreach" und "using" nicht anwendbar sind (z.B. unterstützen einige Enumeratoren verschachtelte Aufzählungen). Ein sorgfältig entworfener Finalizer könnte eine Möglichkeit bieten, mit diesem Szenario umzugehen.

Im Übrigen würde ich verlangen, dass jede Aufzählung, die durch Aktualisierungen fortgesetzt werden soll, die generische IEnumerable(of T) verwenden muss; die nicht-generische Form, die nicht mit iDisposable umgehen kann, müsste eine Ausnahme auslösen, wenn die Sammlung geändert wird.

3voto

Reed Copsey Punkte 536986

Nehmen wir jedoch an, ich habe ein Objekt, das einige Ereignisse abonniert, und ein anderes Objekt, das die Ereignisse tatsächlich verwendet. Wenn das letztere Objekt für die Garbage Collection in Frage kommt, möchte ich, dass sich das erstere Objekt so schnell wie möglich von den Ereignissen abmeldet. Beachten Sie, dass das erstgenannte Objekt erst dann für die Finalisierung in Frage kommt, wenn es von keinem lebenden Objekt mehr abonniert wird.

Wenn das "letztere Objekt" dasjenige ist, das die Ereignisse verwendet, und das "erstere" Objekt dasjenige ist, das die Ereignisse abonniert, muss das "erstere" Objekt eine Möglichkeit haben, die Ereignisinformationen an das "letztere" Objekt weiterzugeben - das heißt, es muss einen Verweis auf "letzteres" haben. Wahrscheinlich wird dies das "letztere" Objekt davon abhalten immer ein GC-Kandidat zu sein.


Abgesehen davon würde ich empfehlen, diese Art der verwalteten Ressourcendeallokation über den Finalizer zu vermeiden, es sei denn, es ist absolut notwendig. Die Architektur, die Sie beschreiben, scheint sehr zerbrechlich und sehr knifflig, richtig zu bekommen. Dies ist wahrscheinlich ein besserer Kandidat für IDisposable, mit dem Finalizer ist die "last ditch" cleanup Aufwand.

Obwohl IDer Einwegartikel geht es in der Regel um die Freigabe nativer Ressourcen - es kann aber auch um die Freigabe jeder anderen Ressource gehen, einschließlich Ihrer Abonnementinformationen.

Außerdem würde ich versuchen, eine einzige globale Sammlung von Objektreferenzen zu vermeiden - es könnte sinnvoller sein, Ihre Objekte intern nur eine WeakReference . Sobald das "letztere" Objekt abgeholt wird, wäre die WeakReference des "ersteren" Objekts nicht mehr gültig. Wenn das nächste Mal ein Ereignis abonniert wird und die interne WeakReference nicht mehr gültig ist, können Sie sich einfach selbst abmelden. Keine Notwendigkeit für globale Warteschlangen, Listen usw. - es sollte einfach funktionieren...

0voto

JMarsch Punkte 21084

Lassen Sie mich sicherstellen, dass ich verstehe - sind Sie besorgt über Lecks von Ereignisabonnenten, die bei einem gesammelten Ereignisverleger abonniert bleiben?

Wenn das der Fall ist, dann brauchen Sie sich keine Sorgen zu machen.

Ich gehe davon aus, dass das "erste" Objekt der Ereignisteilnehmer und das "zweite" Objekt der Ereignisveröffentlicher ist (der das Ereignis auslöst):

Der einzige Grund, warum der Abonnent (Ersterer) "abonniert" ist, ist, dass Sie ein Delegatenobjekt erstellt und diesen Delegaten an den Herausgeber ("Letzterer") weitergegeben haben.

Die Delegatenmitglieder enthalten einen Verweis auf das Subscriber-Objekt und auf die Methode des Subscribers, die ausgeführt wird. Es gibt also eine Verweiskette, die wie folgt aussieht: Publisher --> Delegate --> Subscriber (Publisher verweist auf Delegate, dieser auf Subscriber). Es ist eine 1-Wege-Kette - der Abonnent hat keinen Verweis auf den Delegaten.

Die einzige Wurzel, die den Delegierten beibehält, ist also die des Herausgebers ("latter"). Wenn letzterer für GC in Frage kommt, wird auch der Delegierte gelöscht. Es sei denn, es gibt eine spezielle Aktion, die Sie für Ihre Abonnenten zu nehmen, wenn sie sich abmelden, werden sie effektiv abgemeldet, wenn der Delegat gesammelt wird -- es gibt kein Leck).

Editer

Ausgehend von den Kommentaren von supercat klingt es so, als ob das Problem darin besteht, dass der Verleger den Abonnenten am Leben erhält.

Wenn das das Problem ist, dann werden Ihnen Finalizer nicht helfen. Grund: Ihr Verleger hat einen echten, echten Verweis auf Ihren Abonnenten (über den Delegaten), und der Verleger ist verwurzelt (andernfalls wäre er für GC geeignet), so dass Ihre Abonnenten verwurzelt sind und nicht für Finalisierung oder GC in Frage kommen.

Wenn Sie Probleme mit dem Verleger haben, der den Abonnenten am Leben erhält, würde ich vorschlagen, dass Sie nach weak-ref events suchen. Hier sind ein paar Links, um Ihnen den Einstieg zu erleichtern: http://www.codeproject.com/KB/cs/WeakEvents.aspx http://www.codeproject.com/KB/architecture/observable_property_patte.aspx .

Ich hatte auch einmal damit zu tun. Die meisten der effektiven Muster beinhalten die Änderung des Herausgebers, so dass es eine schwache Referenz auf den Delegaten hält. Dann haben Sie ein neues Problem -- der Delegat ist nicht verwurzelt, und Sie hvae, um es irgendwie am Leben zu halten. Die obigen Artikel tun wahrscheinlich etwas in dieser Richtung. Einige Techniken verwenden Reflection.

Ich habe einmal eine Technik angewandt, die nicht auf Reflexion beruht. Sie erforderte allerdings, dass man in der Lage sein musste, sowohl im Herausgeber als auch im Teilnehmer Änderungen am Code vorzunehmen. Wenn Sie ein Beispiel für diese Lösung sehen möchten, lassen Sie es mich wissen.

0voto

FMM Punkte 4219

Ich werde die Objekte "Verleger" und "Abonnent" nennen und mein Verständnis des Problems neu formulieren:

In C# hält der Verleger (effektiv) Verweise auf die Abonnenten und verhindert so, dass die Abonnenten garbage collected werden. Was kann ich tun, so dass die Abonnenten-Objekte Garbage Collect werden können, ohne explizit die Abonnements zu verwalten?

Als erstes würde ich empfehlen, Folgendes zu tun alles, was ich konnte um diese Situation von vornherein zu vermeiden. Ich gehe jetzt davon aus, dass Sie das getan haben, da Sie die Frage ohnehin gestellt haben =)

Als Nächstes würde ich empfehlen, die Add- und Remove-Accessors des/der Publisher-Ereignisses/Ereignisse zu verknüpfen und eine Sammlung von WeakReferences zu verwenden. Dann können Sie diese Abonnements automatisch aushängen, wenn das Ereignis aufgerufen wird. Hier ist ein extrem rau, ungeprüft Beispiel:

private List<WeakReference> _eventRefs = new List<WeakReference>();

public event EventHandler SomeEvent
{
    add
    {
        _eventRefs.Add(new WeakReference(value));
    }
    remove
    {
        for (int i = 0; i < _eventRefs; i++)
        {
            var wRef = _eventRefs[i];
            if (!wRef.IsAlive)
            {
                _eventRefs.RemoveAt(i);
                i--;
                continue;
            }

            var handler = wRef.Target as EventHandler;
            if (object.ReferenceEquals(handler, value))
            {
                _eventRefs.RemoveAt(i);
                i--;
                continue;
            }
        }
    }
}

0voto

FMM Punkte 4219

Versuchen wir das noch einmal. Können Sie Ihre Event-Handler zu Ihrem Publisher wie folgt hinzufügen:

var pub = new Publisher();
var sub = new Subscriber();
var ref = new WeakReference(sub);

EventHandler handler = null; // gotta do this for self-referencing anonymous delegate

handler = (o,e) =>
{
    if(!ref.IsAlive)
    {
        pub.SomeEvent -= handler; // note the self-reference here, see comment above
        return;
    }

    ((Subscriber)ref.Target).DoHandleEvent();
};

pub.SomeEvent += handler;

Auf diese Weise behält Ihr Delegat keinen direkten Verweis auf den Abonnenten und hakt sich automatisch ab, wenn der Abonnent abgeholt wird. Sie könnten dies als privates statisches Mitglied der Subscriber-Klasse implementieren (für die Zwecke der Kapselung), stellen Sie einfach sicher, dass es statisch ist, um zu verhindern, dass Sie versehentlich einen direkten Verweis auf das "this"-Objekt behalten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X