Was ist die richtige Art von hinzufügen thread-Sicherheit auf ein IDisposable-Objekt?
Stellen Sie sich eine Umsetzung der IDisposable
- Schnittstelle, die hat einige öffentliche Methoden.
Wenn eine Instanz dieses Typs wird von mehreren threads gemeinsam genutzt und einer der threads kann es entsorgen, was ist der beste Weg, um sicherzustellen, dass die anderen threads nicht versuchen zu arbeiten mit der Instanz nach entsorgt? In den meisten Fällen, nachdem das Objekt veräußert, dessen Methoden müssen sich dessen bewusst sein und werfen die ObjectDisposedException
oder vielleicht InvalidOperationException
oder zumindest informieren der aufrufende code für, etwas falsch zu machen. Brauche ich die Synchronisation für jeder Methode - vor allem rund um das prüfen, wenn es entsorgt wird? Tun alle IDisposable
Implementierungen mit anderen öffentlichen Methoden brauchen werden, um thread-sicher?
Hier ist ein Beispiel:
public class DummyDisposable : IDisposable
{
private bool _disposed = false;
public void Dispose()
{
_disposed = true;
//actual dispose logic
}
public void DoSomething()
{
//maybe synchronize around the if block?
if (_disposed)
{
throw new ObjectDisposedException("The current instance has been disposed!");
}
//DoSomething logic
}
public void DoSomethingElse()
{
//Same sync logic as in DoSomething() again?
}
}
- Ich denke, Sie mischen Entsorgen vs Finalizer. Die Dokumente sind so zu erklären, das gewünschte Verhalten ganz gut:msdn.microsoft.com/de-de/library/...
- Nein, bin ich nicht, aber da Sie erwähnt -
Finalize()
wird von einer separaten GC-thread soweit ich mich erinnere. Jedoch, Finalize wird aufgerufen, wenn es keine lebendig Referenz auf das Objekt, also thread-Sicherheit sollte nicht eine überlegung sein dann. In MS best practices es wird empfohlen, rufen Sie Dispose innen Abschließen, oder zumindest fast die gleiche Logik - siehe link. Dies könnte dazu führen, aufrufende thread-Sicherheit-beachten Sie code innerhalb derFinalize()
Methode sowieso. - Ich fügte hinzu, Beispiel-Implementierung zu klären meiner Frage
- Es sollte bis zu dem thread initialisiert ein Wegwerf-Objekt zum ausführen der cleanup, nachdem es nicht mehr benötigt wird. Warum lässt du einige andere Threads entsorgen Sie es? Sie können nur bekommen, selbst in Schwierigkeiten auf diese Weise.
- Ich wollte einige Allgemeine Hinweise, die Materie, nicht mit einem bestimmten Beispiel. Sie haben Recht, in den meisten Fällen den thread erstellen sollte die Bereinigung seiner Ressourcen, aber dies kann nicht der Fall sein, in einigen Szenarien.
- Manchmal ist die Letzte Nutzung eines Objekts kann auf einem anderen thread als dem die es schafft. Zum Beispiel, angenommen, man will asynchron spielen einen sound aus einer Datei, schließen der Datei, nachdem die Wiedergabe abgeschlossen ist. Der thread, der die Datei öffnet, können haben besseres zu tun, als zu warten, um den Ton zu beenden.
- wenn kein anderer thread Los ist, die Datei zu verwenden, dann denke ich, es ist ok. Aber in diesem Fall sehe ich auf den Punkt in den Haupt-thread öffnen der gleichen Datei, entweder. Öffnen Sie es, spielen Sie es, schließen Sie es. Und dann machen Sie den gesamten Auftrag in einem anderen thread, wenn Sie wollen. Ich glaube nicht, dass ich jemals begegnet einem Szenario, wo möchte ich einige andere Threads zu entsorgen, ein Objekt instanziiert anderswo. Es bricht SRP, wenn nichts anderes.
- Obwohl es möglich ist, widmen Sie einen thread zu einem längst Laufenden Betrieb, und haben es warten für jeden Teil der operation abgeschlossen ist, bevor Sie fortfahren, um zum nächsten Teil, eine effizientere Muster in vielen Fällen ist die Verwendung von asynchronen callbacks ausgelöst von Threadpool-threads. Nutzt man eine "vollständige Wiedergabe" Rückruf schließen Sie die audio-Datei, ist es durchaus möglich, dass die Datei könnte sich geschlossen in einem thread, die gar nicht existieren, wenn die Datei geöffnet wurde, oder, dass der thread, der die Datei geöffnet hat, könnte aufhören zu existieren, bevor die Wiedergabe beendet und die Datei geschlossen.
- Auch, wie ich bemerkte auf einer anderen Antwort, es gibt einige Fälle, wo der natürlichste Weg zum Abbruch einer blockierenden I/O-operation ist, entsorgen Sie die Ressource aus darunter. Wenn das scheint eklig, beachten Sie, dass einige Objekte ermöglichen, dass Operationen abgebrochen werden, ohne zu stören das gesamte system Staat, sondern die Objekte, deren Operationen abgebrochen werden, werden Sie unbrauchbar. Wenn etwas zu tun auf ein Objekt, wird es nutzlos, diese Aktion kann auch über den Objekt -. Wenn ein Objekt angeordnet ist, während eine seiner Methoden ist warten, dass etwas geschieht, macht es wenig Sinn für Sie zu halten, zu warten.
- Wenn Abbruch einer operation auf ein Objekt, wird es nutzlos, und bei der Entsorgung ein Objekt sollte dazu führen, dass ein blockierender Vorgang abgebrochen werden, dann Entsorgen Sie das Objekt scheint eine gute Möglichkeit zum Abbrechen einer operation.
- Ich weiß, es ist ein altes Thema, aber ich habe eine Korrektur zum hinzufügen zu Ihrem Kommentar: "Finalize wird aufgerufen, wenn es keine lebendig Referenz auf das Objekt, also thread-Sicherheit sollte nicht eine überlegung sein dann." Leider, Finalizer, dass Sie müssen Sorge um die thread-Sicherheit auch wenn die Klasse ist nicht dazu gedacht, den thread-sicher auf der Außenseite. Dies ist, weil der finalizer kann ausgeführt werden, auch während eine andere Methode der Klasse wird noch ausgeführt! Weitere Informationen finden Sie unter blogs.msdn.microsoft.com/oldnewthing/20100810-00/?p=13193 und vor allem blogs.msdn.microsoft.com/oldnewthing/20100813-00/?p=13153.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Die einfachste Sache, die Sie tun können, ist daneben die privaten entsorgt variable als
volatile
und überprüfen Sie es am Anfang Ihrer Methoden. Sie können dann werfen Sie einenObjectDisposedException
wenn das Objekt bereits veräußert worden.Gibt es zwei Vorbehalte zu diesem:
Sollten Sie nicht throw eine
ObjectDisposedException
wenn die Methode als event-handler. Stattdessen sollten Sie nur anmutig Ausstieg aus der Methode, wenn das möglich ist. Der Grund dafür ist, dass es existiert eine race condition, wo Ereignisse ausgelöst werden können, nachdem Sie sich davon Abmelden. (Siehe dieser Artikel von Eric Lippert für weitere Informationen.)Diese nicht aufhören, Ihre Klasse aus wird entsorgt, während Sie in der Mitte der Ausführung eines Ihrer Klasse Methoden. Also, wenn Ihre Klasse-Instanz-Mitglieder kann nicht zugegriffen werden, nach der Entsorgung, sind Sie gehen zu müssen, um das setup einige locking-Verhalten zu gewährleisten, dass der Zugang zu diesen Ressourcen gesteuert werden.
Microsoft-Führung um IDisposable sagt, Sie sollten prüfen, die darauf angeordnet sind sämtliche Methoden, aber ich habe keine persönlich fand dies für notwendig. Die Frage ist wirklich, ist etwas, das werfen einer exception oder die Ursache für unbeabsichtigte Nebenwirkungen, wenn Sie zulassen, dass eine Methode ausgeführt werden, nachdem die Klasse entsorgt wird. Wenn die Antwort ja ist, werden Sie brauchen, um einige Arbeit zu tun, um sicherzustellen, dass das nicht passiert.
In Bezug auf, ob alle IDisposable-Klassen sollte thread-sicher: Nein. Die meisten Anwendungsfälle für Einweg-Klassen umfassen Sie immer nur zugegriffen, die von einem einzelnen thread.
Dass gesagt wird, Sie Mai wollen untersuchen, warum Sie benötigen, um Ihre Einweg-Klasse thread-sicher, wie es fügt eine Menge zusätzliche Komplexität. Gibt es vielleicht eine Alternative Implementierung erlaubt, dass Sie nicht haben, um sorgen um die thread-Sicherheit in Ihre Einweg-Klasse.
Meisten BCL-Implementierungen Verfügen, sind nicht thread-sicher. Die Idee ist, dass es an den Aufrufer zu Entsorgen, um sicherzustellen, dass niemand anderes mit dem Instanz mehr, bevor es Entsorgt wird. In anderen Worten, es schiebt die Synchronisation Verantwortung nach oben. Dies macht Sinn, da sonst die jetzt alle Ihre anderen Verbraucher behandeln müssen, die Grenze Fall, wo das Objekt wurde Entsorgt, während Sie es.
Sagte, dass, wenn Sie möchten, eine thread-sichere Einweg-Klasse, können Sie einfach erstellen Sie eine Sperre um jede öffentliche Methode (einschließlich zu Entsorgen), die mit einem Scheck für _disposed an der Spitze. Dies kann komplizierter werden, wenn Sie über längere Zeit mit Methoden, in denen Sie nicht möchten, halten Sie die Sperre für die gesamte Methode.
ObjectDisposedException
in diesem FallIch Neige dazu, verwenden Sie eine ganze Zahl statt einer boolean als Ihr Feld für die Speicherung der entsorgt status, weil Sie dann verwenden können, die thread-safe-Interlocked-Klasse zu testen, ob Dispose bereits aufgerufen wurde.
Etwas wie dieses:
Dadurch wird sichergestellt, dass die Entsorgung der code wird nur einmal aufgerufen, egal wie oft die Methode aufgerufen wird, und ist absolut thread-sicher.
Dann jeder Methode können ganz einfach verwenden Sie diese Methode aufrufen, die als Barriere check:
Hinblick auf die Synchronisierung von jeder Methode - werden Sie sagen, eine einfache Barriere-check nicht, dass Sie aufhören wollen anderen threads, die möglicherweise bereits ausgeführten code in die Instanz. Dies ist ein komplexeres problem. Ich weiß nicht, was dein code tut, aber überlegen Sie, wenn Sie wirklich brauchen, dass - wird eine einfache Barriere-check nicht?
Wenn Sie einfach nur gemeint bezüglich der entsorgt schauen Sie selbst, mein Beispiel oben ist in Ordnung.
EDIT: Antwort auf die Bemerkung "Was ist der Unterschied zwischen diesem und ein volatile bool flag? Es ist leicht verwirrend, wenn ein Feld mit dem Namen somethingCount und lassen Sie es halten Sie 0 und 1 nur die Werte"
Flüchtig ist zur Sicherung der read-oder write-operation die operation ist atomar und sicher. Es macht nicht den Prozess der Zuordnung und einen Wert überprüfen thread-sicher. So, zum Beispiel, der folgende Code ist nicht thread-sicher trotz der volatilen:
Das problem hier ist, dass, wenn zwei Fäden eng zusammen, die zunächst überprüfen könnte, _disposed, Lesen Sie false, geben Sie den code-block und Holen Sie ausgeschaltet, bevor Sie _disposed zu wahren. Die zweite prüft dann _disposed, sieht falsch und tritt auch die code-block.
Verwendung von Interlocked sorgt zum einen für die Zuweisung und die anschließende lese, sind einer einzigen atomaren operation.
somethingCount
und lassen Sie es halten Sie 0 und 1 nur die Werte.ThrowIfDisposed
. Dies wäre ein häufiger Fall IMHO, wenn du dein Objekt, das an mehreren Fäden.Dispose
auftreten "natürlich" ist, wenn Abbruch-I/O-Operationen. Wenn ein thread eine blockierende Lesen auf einemSocket
zum Beispiel, und ein anderer thread entscheidet der erste thread sollte nicht noch länger warten (z.B. weil der Benutzer auf "cancel") der richtige Weg für den zweiten thread um die Blockierung der erste thread ist zu Entsorgen die Buchse (die Ursache der Blockierung Lesen zu stoppen und warten, und sofort werfen eine Ausnahme). Wenn der erste thread war das Los der Steckdose, nachdem es beendet seine Operationen in threads könnten, entsorgen Sie gleichzeitig.volatile
für_disposeCount
?Interlocked
atomar ist.Ich lieber mit ganzen zahlen und
Interlocked.Exchange
oderInterlocked.CompareExchange
auf ein integer-Objekt geben Sie "entsorgt" oder "state" - Variablen; verwende ichenum
wennInterlocked.Exchange
oderInterlocked.CompareExchange
könnte, mit solchen Typen, aber ach Sie kann nicht.Einen Punkt, der die meisten Diskussionen über IDisposable und Finalizer umhin zu erwähnen, ist, dass, während ein Objekt der finalizer sollte nicht laufen, während IDisposable.Dispose() ist in Arbeit, es gibt keine Möglichkeit für eine Klasse, um Objekte von Ihrem Typ aus erklärt wird, tot und dann auferstanden. Um sicher zu sein, wenn außerhalb von code erlaubt, dass dies geschieht, gibt es offensichtlich keine Voraussetzung, dass das Objekt "normal arbeiten", aber die Dispose und finalize-Methoden, die sollte gut genug geschützt, um sicherzustellen, dass Sie nicht verfälschen andere - Objekte " Zustand, was wiederum erfordern in der Regel entweder sperren oder
Interlocked
Operationen auf Objekt Variablen.FWIW, Ihre Beispielcode entspricht meiner Kollegen und ich in der Regel mit diesem Thema befassen. Wir in der Regel definieren ein eigenes
CheckDisposed
Methode für die Klasse:Dann nennen wir die
CheckDisposed()
Methode an der Spitze aller öffentlichen Methoden.Thread, wenn Streit über die Entsorgung wird als wahrscheinlich angesehen, eher als ein Fehler aufgetreten ist, werde ich auch eine öffentliche
IsDisposed()
Methode (Ähnlich Kontrolle.IsDisposed).Update: Basierend auf die Kommentare mit Bezug auf den Wert der
isDisposed
flüchtigen, beachten Sie, dass der "Zaun" Problem ist eher trivial gegeben, wie ich dieCheckDisposed()
Methode. Es ist im wesentlichen ein tool zur Fehlerbehebung für schnell fangen den Fall, wo der code ruft eine öffentliche Methode auf das Objekt, nachdem es bereits veräußert worden. AufrufCheckDisposed()
am Beginn einer öffentlichen Methode in keiner Weise garantiert, dass das Objekt nicht entsorgt werden innerhalb dieser Methode. Wenn ich bedenke, dass es eine inhärente Risiko in meiner Klasse design, im Gegensatz zu einer Fehlerbedingung, die ich nicht berücksichtigt, für, dann nutze ich die oben genanntenIsDisposed
- Methode zusammen mit der entsprechenden Verriegelung.if
Zustand.volatile
,Interlocked.CompareExchange()
möglicherweise ein besserer Ansatz, ich glaube, es kümmert sich um das Fechten.Müssen Sie Sperre, die jeden Zugriff auf die ressource, die Sie dabei sind zu entsorgen. Ich habe auch das Dispose-Muster, die ich normalerweise verwenden.
GC.SuppressFinalize(this)
im finalizer - ist es nicht zu spät, wenn die GC ist bereits die Entsorgung der Instanz? Vielleicht rufen Sie diese nach dem erfolgreichen entsorgen zu erleichtern, die GC?