Frage

Lassen Sie uns sagen, dass ich eine Klasse, die die IDisposable implementiert Schnittstelle. So etwas wie folgt aus:

http://www.flickr.com/photos/garthof/3149605015/

MyClass verwendet einige nicht verwalteten Ressourcen, damit die Entsorgen () Methode von IDisposable freigibt diese Ressourcen. MyClass sollte wie folgt verwendet werden:

using ( MyClass myClass = new MyClass() ) {
    myClass.DoSomething();
}

Nun möchte ich eine Methode implementieren, ruft DoSomething () asynchron. Ich füge eine neue Methode MyClass :

http://www.flickr.com/photos/garthof/3149605005/

Nun, von der Client-Seite MyClass sollte wie folgt verwendet werden:

using ( MyClass myClass = new MyClass() ) {
    myClass.AsyncDoSomething();
}

Allerdings, wenn ich etwas nicht tun, sonst, das als das Objekt scheitern könnte myClass könnte, bevor entsorgt werden DoSomething () genannt wird (und ein unerwartetes werfen < strong> ObjectDisposedException ). So ist der Aufruf des Entsorgen () Methode (entweder implizit oder explizit) soll bis zum asynchronen Aufruf DoSomething () verzögert werden, erfolgt.

Ich denke, der Code in der Entsorgen () Methode ausgeführt werden sollte in einer asynchronen Art und Weise, und nur einmal alle asynchronen Anrufe werden aufgelöst . Ich würde gerne wissen, welche der beste Weg, um dies zu erreichen sein könnte.

Danke.

Hinweis: Aus Gründen der Einfachheit, habe ich nicht in den Einzelheiten, wie Methode Dispose () eingegeben wird, implementiert. Im wirklichen Leben folgen ich in der Regel die Muster zu entsorgen.


UPDATE: Vielen Dank für Ihre Antworten so viel. Ich schätze Ihre Bemühungen. Wie chakrit hat kommentiert , ich brauche, dass mehr Anrufe auf das Asynchron DoSomething können gemacht werden, . Idealerweise sollte in etwa so funktionieren:

using ( MyClass myClass = new MyClass() ) {

    myClass.AsyncDoSomething();
    myClass.AsyncDoSomething();

}

ich das Zählen Semaphore studieren werde, so scheint es, was ich suche. Es könnte auch ein Design-Problem sein. Wenn ich es bequem finden, werde ich mit Ihnen einige Bits des realen Fall teilen und was MyClass wirklich der Fall ist.

War es hilfreich?

Lösung 6

Also, meine Idee ist, zu halten, wie viele AsyncDoSomething () anhängig zu vollenden, und nur zu veräußern, wenn diese Zählung auf Null erreicht. Mein erster Ansatz ist:

public class MyClass : IDisposable {

    private delegate void AsyncDoSomethingCaller();
    private delegate void AsyncDoDisposeCaller();

    private int pendingTasks = 0;

    public DoSomething() {
        // Do whatever.
    }

    public AsyncDoSomething() {
        pendingTasks++;
        AsyncDoSomethingCaller caller = new AsyncDoSomethingCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoSomethingCallback ), caller);
    }

    public Dispose() {
        AsyncDoDisposeCaller caller = new AsyncDoDisposeCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoDisposeCallback ), caller);
    }

    private DoDispose() {
        WaitForPendingTasks();

        // Finally, dispose whatever managed and unmanaged resources.
    }

    private void WaitForPendingTasks() {
        while ( true ) {
            // Check if there is a pending task.
            if ( pendingTasks == 0 ) {
                return;
            }

            // Allow other threads to execute.
            Thread.Sleep( 0 );
        }
    }

    private void EndDoSomethingCallback( IAsyncResult ar ) {
        AsyncDoSomethingCaller caller = (AsyncDoSomethingCaller) ar.AsyncState;
        caller.EndInvoke( ar );
        pendingTasks--;
    }

    private void EndDoDisposeCallback( IAsyncResult ar ) {
        AsyncDoDisposeCaller caller = (AsyncDoDisposeCaller) ar.AsyncState;
        caller.EndInvoke( ar );
    }
}

Einige Probleme können auftreten, wenn zwei oder mehr Threads versuchen, die pendingTasks Variable gleichzeitig, so dass die Sperre lesen / schreiben Schlüsselwort verwendet werden soll, Rennbedingungen zu verhindern:

public class MyClass : IDisposable {

    private delegate void AsyncDoSomethingCaller();
    private delegate void AsyncDoDisposeCaller();

    private int pendingTasks = 0;
    private readonly object lockObj = new object();

    public DoSomething() {
        // Do whatever.
    }

    public AsyncDoSomething() {
        lock ( lockObj ) {
            pendingTasks++;
            AsyncDoSomethingCaller caller = new AsyncDoSomethingCaller();
            caller.BeginInvoke( new AsyncCallback( EndDoSomethingCallback ), caller);
        }
    }

    public Dispose() {
        AsyncDoDisposeCaller caller = new AsyncDoDisposeCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoDisposeCallback ), caller);
    }

    private DoDispose() {
        WaitForPendingTasks();

        // Finally, dispose whatever managed and unmanaged resources.
    }

    private void WaitForPendingTasks() {
        while ( true ) {
            // Check if there is a pending task.
            lock ( lockObj ) {
                if ( pendingTasks == 0 ) {
                    return;
                }
            }

            // Allow other threads to execute.
            Thread.Sleep( 0 );
        }
    }

    private void EndDoSomethingCallback( IAsyncResult ar ) {
        lock ( lockObj ) {
            AsyncDoSomethingCaller caller = (AsyncDoSomethingCaller) ar.AsyncState;
            caller.EndInvoke( ar );
            pendingTasks--;
        }
    }

    private void EndDoDisposeCallback( IAsyncResult ar ) {
        AsyncDoDisposeCaller caller = (AsyncDoDisposeCaller) ar.AsyncState;
        caller.EndInvoke( ar );
    }
}

Ich sehe ein Problem mit diesem Ansatz. Da die Freigabe von Ressourcen asynchron durchgeführt wird, könnte wie folgt funktionieren:

MyClass myClass;

using ( myClass = new MyClass() ) {
    myClass.AsyncDoSomething();
}

myClass.DoSomething();

Wenn das erwartete Verhalten soll ein ObjectDisposedException zu starten, wenn DoSomething () außerhalb der aufgerufen wird mit Klausel. Aber ich finde das nicht schlimm genug, um diese Lösung zu überdenken.

Andere Tipps

Es sieht aus wie Sie das ereignisbasierte asynchrone Muster verwenden ( siehe hier für weitere Informationen über .NET Asynchron-Muster ) so, was man typischerweise ist ein Ereignis für die Klasse, die ausgelöst wird, wenn die Asynchron Operation namens DoSomethingCompleted abgeschlossen ist (das AsyncDoSomething beachten sollte wirklich DoSomethingAsync genannt werden, um das Muster korrekt zu folgen ). Mit diesem Ereignis ausgesetzt könnten Sie schreiben:

var myClass = new MyClass();
myClass.DoSomethingCompleted += (sender, e) => myClass.Dispose();
myClass.DoSomethingAsync();

Die andere Alternative ist das IAsyncResult Muster zu verwenden, in dem Sie einen Delegierten übergeben können, die die dispose-Methode zum AsyncCallback Parameter (weitere Informationen zu diesem Muster ist oben auch in der Seite) aufruft. In diesem Fall würden Sie BeginDoSomething und EndDoSomething Methoden statt DoSomethingAsync haben, und es wäre etwas nennen wie ...

var myClass = new MyClass();
myClass.BeginDoSomething(
    asyncResult => {
                       using (myClass)
                       {
                           myClass.EndDoSomething(asyncResult);
                       }
                   },
    null);        

Aber je nachdem, welche Art und Weise Sie es tun, müssen Sie einen Weg für die Anrufer mitgeteilt werden, dass die Asynchron-Vorgang abgeschlossen ist, so dass es von dem Objekt zur richtigen Zeit verfügen kann.

Async Methoden in der Regel einen Rückruf haben so dass Sie eine Aktion auf Ergänzung zu tun tun. Wenn dies der Fall ist, wäre es so etwas wie diese:

// The async method taks an on-completed callback delegate
myClass.AsyncDoSomething(delegate { myClass.Dispose(); });

Eine andere Möglichkeit, um dies ist ein Asynchron-Wrapper:

ThreadPool.QueueUserWorkItem(delegate
{
    using(myClass)
    {
        // The class doesn't know about async operations, a helper method does that
        myClass.DoSomething();
    }
});

Ich würde den Code irgendwie nicht ändern für async verfügt zu ermöglichen. Stattdessen würde ich sicherstellen, wenn der Anruf zu AsyncDoSomething gemacht wird, wird es eine Kopie aller Daten haben es ausführen muss. Das Verfahren sollte zur Reinigung alle, wenn seine Ressourcen verantwortlich sein.

Sie könnten einen Callback-Mechanismus hinzufügen und eine Bereinigungsfunktion als Rückruf übergeben.

var x = new MyClass();

Action cleanup = () => x.Dispose();

x.DoSomethingAsync(/*and then*/cleanup);

, aber dies würde Problem darstellen, wenn Sie mehr Asynchron-off ruft die gleiche Objektinstanz ausgeführt werden sollen.

Eine Möglichkeit, eine einfache Zählen Semaphore mit der Semaphore Klasse die Anzahl der zählen Asynchron-Jobs ausgeführt werden.

Fügen Sie den Zähler auf MyClass und auf jedem AsyncWhatever Anrufe Zähler erhöht, auf Ausfahrten es decerement. Wenn die Semaphore 0 ist, dann ist die Klasse ist bereit, entsorgt werden.

var x = new MyClass();

x.DoSomethingAsync();
x.DoSomethingAsync2();

while (x.RunningJobsCount > 0)
    Thread.CurrentThread.Sleep(500);

x.Dispose();

Aber ich bezweifle, dass wäre der ideale Weg. Ich rieche ein Design-Problem. Vielleicht ist dies eine erneute gedacht MyClass Designs vermeiden könnte?

Könnten Sie etwas wenig MyClass Implementierung teilen? Was sie tun soll?

Ich halte es für bedauerlich, dass Microsoft nicht als Teil des IDisposable Vertrages erforderlich war, die Implementierungen ermöglichen sollten Dispose von jedem Threadkontext aufgerufen werden, da es keine vernünftige Art und Weise ist die Schaffung eines Objekts, den Fortbestand des Gewindes erzwingen Kontext, in dem sie erstellt wurde. Es ist möglich, Code so zu gestalten, dass der Faden, die irgendwie für das Objekt beobachten wird ein Objekt erstellt obsolet und kann an seiner Bequemlichkeit Dispose, und das so, wenn der Faden länger für alles benötigt wird, kein anderes wird es bleiben, um bis alle entsprechenden Objekte Disposed, aber ich glaube nicht, dass es ein Standard-Mechanismus, der nicht ein spezielles Verhalten erfordert auf Seiten des Fadens die Dispose zu schaffen.

Ihre beste Wette ist wahrscheinlich all Objekte von Interesse in einem gemeinsamen Thread erstellt hat (vielleicht den UI-Thread), versucht, zu garantieren, dass der Faden um die Lebensdauer der Objekte von Interesse bleiben wird, und verwenden Sie so etwas wie Control.BeginInvoke die Objekte zur Verfügung zu beantragen. Vorausgesetzt, dass weder Objekterstellung noch Bereinigung für längere Zeit blockiert, die ein guter Ansatz sein können, aber wenn entweder Betrieb ein anderer Ansatz vielleicht notwendig sein blockieren könnte kann [eine versteckte Dummy-Form mit einem eigenen Thread eröffnen, so kann man verwenden Control.BeginInvoke dort].

Alternativ, wenn Sie die Kontrolle über die IDisposable Implementierungen haben, entwerfen sie, so dass sie sicher asynchron abgefeuert werden können. In vielen Fällen, das wird „einfach funktionieren“ vorausgesetzt niemand das Element zu verwenden versucht, wenn es angebracht ist, aber das ist kaum eine Selbstverständlichkeit. Insbesondere mit vielen Arten von IDisposable, gibt es eine reale Gefahr, dass mehrere Objektinstanzen beide könnten eine gemeinsame Außen Ressource manipulieren [z Dieses Objekt kann List<> von erstellt Instanzen halten, fügen Sie Instanzen dieser Liste, wenn sie aufgebaut sind, und entfernen Sie Instanzen auf Dispose; wenn die Listenoperationen nicht synchronisiert sind, eine asynchrone Dispose könnte korrupt die Liste selbst wenn das Objekt angeordnet ist, ist nicht anders verwendet wird.

BTW, ein nützliches Muster für Objekte asynchron dispose zu ermöglichen, während sie im Einsatz, mit der Erwartung, dass ein derartige Entsorgung alle Vorgänge im verursacht eine Ausnahme bei der ersten geeigneten Gelegenheit zu werfen. Dinge wie Steckdosen funktionieren auf diese Weise. Es kann nicht möglich sein, eine Leseoperation eines vorzeitiges Ausscheiden zu sein, ohne seine Steckdose in einem nutzlosen Zustand zu verlassen, aber wenn die Buchse der ohnehin nie benutzt werden, gibt es keinen Grund für die Lese auf Daten warten zu halten, wenn ein anderer Thread bestimmt hat, dass sollte es aufgeben. IMHO, das ist, wie alle IDisposable Objekte sollten sich bemühen, zu verhalten, aber ich kenne kein Dokument für eine solche allgemeine Muster aufrufen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top