Question

Supposons que ma classe implémente l'interface IDisposable . Quelque chose comme ça:

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

MyClass utilise des ressources non gérées. Par conséquent, la méthode Dispose () de IDisposable libère ces ressources. MyClass doit être utilisé comme suit:

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

Maintenant, je souhaite implémenter une méthode qui appelle DoSomething () de manière asynchrone. J'ajoute une nouvelle méthode à MyClass :

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

Désormais, côté client, MyClass doit être utilisé comme suit:

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

Toutefois, si je ne fais rien d'autre, cela pourrait échouer car l'objet myClass pourrait être supprimé avant que DoSomething () <) ne soit appelé (et jette un < strong> ObjectDisposedException ). Ainsi, l'appel à la méthode Dispose () (implicite ou explicite) doit être différé jusqu'à ce que l'appel asynchrone de DoSomething () soit terminé.

Je pense que le code de la méthode Dispose () doit être exécuté de manière asynchrone, et une fois que tous les appels asynchrones ont été résolus . J'aimerais savoir quel pourrait être le meilleur moyen d'y parvenir.

Merci.

REMARQUE: Par souci de simplicité, je n'ai pas détaillé les détails de la mise en oeuvre de la méthode Dispose (). Dans la vie réelle, je suis généralement le modèle de suppression .

MISE À JOUR: Merci beaucoup pour vos réponses. J'apprécie ton effort. As chakrit a commenté , j'ai besoin de plusieurs appels à la fonction asynchrone. Vous pouvez faire quelque chose . Idéalement, quelque chose comme ceci devrait fonctionner correctement:

using ( MyClass myClass = new MyClass() ) {

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

}

Je vais étudier le sémaphore de comptage, il semble que je cherche. Cela pourrait aussi être un problème de conception. Si cela me convient, je partagerai avec vous quelques éléments de cas réels et ce que MyClass fait réellement.

Était-ce utile?

La solution 6

Mon idée est donc de garder le nombre AsyncDoSomething () en attente d'achèvement et de ne disposer que lorsque ce nombre atteint zéro. Mon approche initiale est:

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

Certains problèmes peuvent survenir si plusieurs threads essaient de lire / écrire la variable waitingTasks . Le mot clé lock doit donc être utilisé pour éviter les situations de concurrence critique:

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

Je vois un problème avec cette approche. Comme la libération des ressources est effectuée de manière asynchrone, cela pourrait fonctionner:

MyClass myClass;

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

myClass.DoSomething();

Lorsque le comportement attendu devrait être de lancer une ObjectDisposedException lorsque DoSomething () est appelé en dehors de la à l'aide de la clause . Mais je ne trouve pas cela assez grave pour repenser cette solution.

Autres conseils

Il semble que vous utilisiez le modèle asynchrone basé sur des événements ( à voir ici Pour plus d’informations sur les modèles async .NET ), il s’agit généralement d’un événement sur la classe qui se déclenche lorsque l’opération asynchrone est terminée et qui porte le nom DoSomethingCompleted (notez que AsyncDoSomething doit réellement être appelé <= > suivre le modèle correctement). Avec cet événement exposé, vous pouvez écrire:

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

L’autre alternative consiste à utiliser le modèle DoSomethingAsync, dans lequel vous pouvez transmettre un délégué appelant la méthode de disposition au paramètre IAsyncResult (pour plus d’informations sur ce modèle, reportez-vous à la page ci-dessus). Dans ce cas, vous auriez des méthodes AsyncCallback et BeginDoSomething au lieu de EndDoSomething, et l'appelleriez quelque chose comme ...

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

Mais quelle que soit la manière dont vous le faites, vous avez besoin d'un moyen pour que l'appelant soit averti que l'opération asynchrone est terminée afin qu'il puisse disposer de l'objet au bon moment.

Les méthodes asynchrones ont généralement un rappel qui vous permet de faire certaines actions à la fin. Si tel est votre cas, cela ressemblerait à ceci:

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

Une autre solution consiste à utiliser un wrapper asynchrone:

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

Je ne modifierais pas le code de manière à permettre une élimination asynchrone. Au lieu de cela, je m'assurerais que lorsque l'appel à AsyncDoSomething est effectué, il disposera d'une copie de toutes les données nécessaires à son exécution. Cette méthode devrait permettre de tout nettoyer si ses ressources le permettent.

Vous pouvez ajouter un mécanisme de rappel et transmettre une fonction de nettoyage en tant que rappel.

var x = new MyClass();

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

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

mais cela poserait problème si vous souhaitez exécuter plusieurs appels asynchrones à partir de la même instance d'objet.

Une solution consisterait à implémenter un dénombrement du sémaphore avec La classe Semaphore pour compter le nombre de travaux asynchrones en cours d'exécution.

Ajoutez le compteur à MyClass et à chaque appel Async, quel que soit l’appel incrémenté, les sorties le décerment. Lorsque le sémaphore est à 0, la classe est prête à être supprimée.

var x = new MyClass();

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

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

x.Dispose();

Mais je doute que ce soit la solution idéale. Je sens un problème de design. Peut-être qu’une nouvelle conception des conceptions de MyClass pourrait éviter cela?

Pourriez-vous partager un peu d'implémentation de MyClass? Qu'est-ce qu'il est censé faire?

Je trouve dommage que Microsoft n'ait pas exigé, dans le cadre du contrat IDisposable, que les implémentations permettent à Dispose d'être appelé à partir de n'importe quel contexte de thread, car rien ne permet de manière rationnelle de créer un objet. existence du contexte de threading dans lequel il a été créé. Il est possible de concevoir du code pour que le thread qui crée un objet surveille l'objet et devienne obsolète et puisse Control.BeginInvoke à sa convenance, et pour que le thread ne soit plus nécessaire, il reste en place jusqu'à ce que tout soit approprié. les objets ont été List<> d, mais je ne pense pas qu'il existe un mécanisme standard qui n'exige pas de comportement particulier du thread créant le <=>.

Votre meilleur choix est probablement de faire en sorte que tous les objets d'intérêt soient créés au sein d'un fil commun (peut-être le fil de l'interface utilisateur), d'essayer de garantir que le fil restera toute la vie des objets d'intérêt et d'utiliser quelque chose comme < => demander la disposition des objets. À condition que ni la création ni le nettoyage d’objet ne bloquent pendant une période prolongée, cela peut être une bonne approche, mais si l’une ou l’autre opération peut bloquer une approche différente, vous devrez peut-être ouvrir une fiche factice masquée avec son propre fil de discussion. utilisez <=> ici].

Sinon, si vous avez le contrôle sur les <=> implémentations, concevez-les de sorte qu'elles puissent être déclenchées en toute sécurité de manière asynchrone. Dans de nombreux cas, cela & "; Fonctionnera seulement &"; à condition que personne n’essaie d’utiliser cet objet lorsqu’il est éliminé, mais c’est loin d’être acquis. En particulier, avec de nombreux types de <=>, il existe un risque réel que plusieurs instances d'objet manipulent toutes les deux une ressource extérieure commune [par exemple. un objet peut contenir une <=> instance créée, ajouter des instances à cette liste lors de la construction et supprimer des instances le <=>; Si les opérations de liste ne sont pas synchronisées, un <=> asynchrone pourrait corrompre la liste même si l'objet en cours d'élimination n'est pas utilisé par ailleurs.

BTW, un modèle utile consiste pour les objets à autoriser la suppression asynchrone lorsqu'ils sont en cours d'utilisation, en espérant que cette suppression entraînera le déclenchement d'une exception par les opérations en cours à la première occasion appropriée. Des choses comme les douilles fonctionnent de cette façon. Il peut ne pas être possible pour une opération de lecture d'être sortie prématurément sans laisser son socket dans un état inutile, mais si le socket ne doit jamais être utilisé de toute façon, il n'y a aucune raison pour que la lecture continue d'attendre les données si un autre thread a déterminé que il devrait abandonner. IMHO, c’est comme ça que tous les <=> objets devraient s’efforcer de se comporter, mais je ne connais aucun document qui appelle à un tel schéma général.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top