Domanda

Diciamo che ho una classe che implementa l'interfaccia IDisposable . Qualcosa del genere:

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

MyClass utilizza alcune risorse non gestite, quindi il metodo Dispose () da IDisposable rilascia tali risorse. MyClass dovrebbe essere usato in questo modo:

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

Ora, voglio implementare un metodo che chiama DoSomething () in modo asincrono. Aggiungo un nuovo metodo a MyClass :

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

Ora, dal lato client, MyClass dovrebbe essere usato in questo modo:

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

Tuttavia, se non faccio altro, ciò potrebbe non riuscire poiché l'oggetto myClass potrebbe essere eliminato prima di chiamare DoSomething () (e lanciare un < strong> ObjectDisposedException ). Pertanto, la chiamata al metodo Dispose () (implicita o esplicita) dovrebbe essere ritardata fino al completamento della chiamata asincrona a DoSomething () .

Penso che il codice nel metodo Dispose () dovrebbe essere eseguito in modo asincrono e solo una volta risolte tutte le chiamate asincrone . Mi piacerebbe sapere quale potrebbe essere il modo migliore per raggiungere questo obiettivo.

Grazie.

NOTA: per semplicità, non ho inserito i dettagli su come viene implementato il metodo Dispose (). Nella vita reale di solito seguo il Schema di smaltimento .


AGGIORNAMENTO: Grazie mille per le tue risposte. Apprezzo il tuo sforzo. As chakrit ha commentato , ho bisogno che sia possibile effettuare più chiamate al DoSomething asincrono . Idealmente, qualcosa del genere dovrebbe funzionare bene:

using ( MyClass myClass = new MyClass() ) {

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

}

Studierò il semaforo del conteggio, sembra quello che sto cercando. Potrebbe anche essere un problema di progettazione. Se lo trovo conveniente, condividerò con voi alcune parti del caso reale e cosa fa veramente MyClass .

È stato utile?

Soluzione 6

Quindi, la mia idea è quella di mantenere quanti AsyncDoSomething () sono in attesa di completamento e smaltire solo quando questo conteggio arriva a zero. Il mio approccio iniziale è:

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 );
    }
}

Alcuni problemi possono verificarsi se due o più thread tentano di leggere / scrivere contemporaneamente la variabile pendingTasks , quindi la parola chiave blocca deve essere utilizzata per prevenire le condizioni di competizione:

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 );
    }
}

Vedo un problema con questo approccio. Poiché il rilascio di risorse viene eseguito in modo asincrono, qualcosa del genere potrebbe funzionare:

MyClass myClass;

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

myClass.DoSomething();

Quando il comportamento previsto dovrebbe essere quello di lanciare una ObjectDisposedException quando DoSomething () viene chiamato al di fuori della clausola usando . Ma non lo trovo abbastanza male per ripensare questa soluzione.

Altri suggerimenti

Sembra che tu stia utilizzando il modello asincrono basato sugli eventi ( vedi qui per ulteriori informazioni sui modelli asincroni .NET ), quindi quello che avresti in genere è un evento sulla classe che viene generato quando l'operazione asincrona viene completata denominata DoSomethingCompleted (nota che AsyncDoSomething dovrebbe davvero essere chiamato <= > seguire correttamente lo schema). Con questo evento esposto puoi scrivere:

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

L'altra alternativa è utilizzare il modello DoSomethingAsync, in cui è possibile passare un delegato che chiama il metodo dispose al parametro IAsyncResult (ulteriori informazioni su questo modello si trovano anche nella pagina sopra). In questo caso avresti AsyncCallback e BeginDoSomething metodi invece di EndDoSomething e lo chiameresti come ...

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

Ma qualunque sia il modo in cui lo fai, hai bisogno di un modo per avvisare il chiamante che l'operazione asincrona è stata completata in modo da poter disporre l'oggetto al momento giusto.

I metodi asincroni di solito hanno un callback che ti consente di fare alcune azioni al termine. Se questo è il tuo caso, sarebbe qualcosa del genere:

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

Un altro modo per aggirare questo è un wrapper asincrono:

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

Non modificherei il codice in qualche modo per consentire le eliminazioni asincrone. Invece mi assicurerei che quando viene effettuata la chiamata a AsyncDoSomething, avrà una copia di tutti i dati necessari per l'esecuzione. Tale metodo dovrebbe essere responsabile della pulizia di tutte le sue risorse.

È possibile aggiungere un meccanismo di callback e passare una funzione di cleanup come callback.

var x = new MyClass();

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

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

ma ciò costituirebbe un problema se si desidera eseguire più chiamate asincrone dalla stessa istanza di oggetto.

Un modo sarebbe implementare un semplice conteggio del semaforo con il Classe semaphore per contare il numero di lavori asincroni in esecuzione.

Aggiungi il contatore a MyClass e su tutte le chiamate Async Qualunque incrementa il contatore, quando esce dal decerement. Quando il semaforo è 0, la classe è pronta per essere eliminata.

var x = new MyClass();

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

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

x.Dispose();

Ma dubito che sarebbe il modo ideale. Sento un problema di progettazione. Forse un ripensamento dei progetti MyClass potrebbe evitarlo?

Potresti condividere un po 'di implementazione di MyClass? Cosa dovrebbe fare?

Ritengo sfortunato che Microsoft non abbia richiesto, nell'ambito del contratto IDisposable, che le implementazioni consentano che Dispose venga chiamato da qualsiasi contesto di threading, dal momento che non esiste alcun modo ragionevole che la creazione di un oggetto possa forzare il continuo esistenza del contesto di threading in cui è stato creato. È possibile progettare il codice in modo che il thread che crea un oggetto controllerà in qualche modo l'oggetto che diventa obsoleto e può Control.BeginInvoke a suo piacimento, e in modo che quando il thread non è più necessario per nient'altro, rimarrà fino a quando tutto appropriato gli oggetti sono stati List<> d, ma non credo che esista un meccanismo standard che non richiede un comportamento speciale da parte del thread che crea <=>.

La tua scommessa migliore è probabilmente quella di avere tutti gli oggetti di interesse creati all'interno di un thread comune (forse il thread dell'interfaccia utente), cercare di garantire che il thread rimanga in vita per tutta la durata degli oggetti di interesse e utilizzare qualcosa come < => per richiedere lo smaltimento degli oggetti. A condizione che né la creazione di oggetti né la pulizia si blocchino per un certo periodo di tempo, questo potrebbe essere un buon approccio, ma se entrambe le operazioni potrebbero bloccare un approccio diverso potrebbe essere necessario [forse aprire un modulo fittizio nascosto con il proprio thread, quindi si può usa <=> lì].

In alternativa, se si ha il controllo sulle <=> implementazioni, progettarle in modo che possano essere attivate in modo sicuro in modo asincrono. In molti casi, ciò & Quot; funzionerà solo & Quot; a condizione che nessuno stia cercando di utilizzare l'oggetto quando viene smaltito, ma non è certo un dato di fatto. In particolare, con molti tipi di <=>, esiste il reale pericolo che più istanze di oggetti possano manipolare una risorsa esterna comune [ad es. un oggetto può contenere un <=> di istanze create, aggiungere istanze all'elenco quando vengono costruite e rimuovere istanze su <=>; se le operazioni dell'elenco non sono sincronizzate, un <=> asincrono potrebbe corrompere l'elenco anche se l'oggetto da smaltire non è altrimenti utilizzato.

A proposito, uno schema utile è che gli oggetti consentano lo smaltimento asincrono mentre sono in uso, con l'aspettativa che tale eliminazione provocherà un'eccezione alla prima opportuna operazione. Cose come le prese funzionano in questo modo. Potrebbe non essere possibile terminare anticipatamente un'operazione di lettura senza lasciare il socket in uno stato inutile, ma se comunque il socket non verrà mai utilizzato, non ha senso che la lettura continui ad attendere i dati se un altro thread ha determinato che dovrebbe arrendersi. IMHO, è così che tutti gli <=> oggetti dovrebbero sforzarsi di comportarsi, ma non conosco nessun documento che richieda un modello così generale.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top