La classe CancellationTokenSource
est jetable. Un rapide coup d’œil dans Reflector prouve l’utilisation de KernelEvent
, une ressource non gérée (très probable). Puisque CancellationTokenSource
n’a pas de finaliseur, si nous ne le éliminons pas, le GC ne le fera pas.
D’un autre côté, si vous examinez les exemples répertoriés dans l’article MSDN Annulation dans les threads gérés , un seul fragment de code dispose du jeton.
Quelle est la bonne façon de s’en débarrasser en code?
using
si vous ne l’attendez pas. Et il est logique d’annuler uniquement si vous n’attendez pas. ContinueWith
à une tâche avec un appel Dispose
, mais est-ce la voie à suivre? .ForAll(x => Console.Write(x))
? Comme il n’a pas quelque chose comme une méthode de Reset
pour nettoyer les IsCancelRequested
et Token
, je suppose que ce n’est pas réutilisable. Chaque fois que vous lancez une tâche (ou une requête PLINQ), vous devez en créer une nouvelle. Est-ce vrai? Si oui, ma question est la suivante: quelle est la stratégie correcte et recommandée pour gérer Dispose
sur ces nombreuses instances de CancellationTokenSource
?
En parlant de savoir s’il est vraiment nécessaire d’appeler Dispose sur CancellationTokenSource
… J’ai eu une fuite de mémoire dans mon projet et il s’est avéré que la solution concernait CancellationTokenSource
.
Mon projet a un service qui lit en permanence la firebase database et exécute différentes tâches, et je transmettais des jetons d’annulation liés à mes employés. Même après le traitement des données, les jetons d’annulation n’ont pas été supprimés.
L’ annulation MSDN dans les threads gérés l’ indique clairement:
Notez que vous devez appeler
Dispose
sur la source de jeton liée lorsque vous en avez fini avec. Pour un exemple plus complet, consultez Comment: écouter les demandes d’annulation multiples .
J’ai utilisé ContinueWith
dans mon implémentation.
J’ai regardé dans ILSpy pour le CancellationTokenSource
mais je ne peux trouver que m_KernelEvent qui est en fait un ManualResetEvent
, qui est une classe wrapper pour un object WaitHandle. Cela devrait être géré correctement par le GC.
Je ne pensais pas que les réponses actuelles étaient satisfaisantes. Après des recherches, j’ai trouvé cette réponse de Stephen Toub ( référence ):
Ça dépend. Dans .NET 4, CTS.Dispose avait deux objectives principaux. Si le WaitHandle de CancellationToken avait été accédé (allouant ainsi paresseusement), Dispose disposera de cette poignée. De plus, si le CTS a été créé via la méthode CreateLinkedTokenSource, Dispose dissociera le CTS des jetons auxquels il était lié. Dans .NET 4.5, Dispose a un objective supplémentaire: si le CTS utilise un temporisateur sous les couvertures (par exemple, CancelAfter a été appelé), le temporisateur sera supprimé.
Il est très rare que le terme CancellationToken.WaitHandle soit utilisé, par conséquent, le nettoyage n’est généralement pas une bonne raison d’utiliser Dispose. Si, toutefois, vous créez votre CTS avec CreateLinkedTokenSource ou si vous utilisez la fonctionnalité de timer de CTS, l’utilisation de Dispose peut être plus efficace.
La partie audacieuse je pense est la partie importante. Il utilise “plus d’impact” ce qui le laisse un peu vague. J’interprète cela comme signifiant qu’appeler Dispose
dans ces situations devrait être fait, sinon utiliser Dispose
n’est pas nécessaire.
Vous devez toujours éliminer CancellationTokenSource
.
Comment s’en débarrasser dépend exactement du scénario. Vous proposez plusieurs scénarios différents.
using
uniquement fonctionne lorsque vous utilisez CancellationTokenSource
sur un travail parallèle que vous attendez. Si c’est votre senario, alors génial, c’est la méthode la plus simple.
Lorsque vous utilisez des tâches, utilisez la tâche ContinueWith
comme vous l’avez indiqué pour vous débarrasser de CancellationTokenSource
.
Pour plinq, vous pouvez utiliser l’ using
car vous l’exécutez en parallèle, mais attendez la fin de tous les opérateurs en parallèle.
Pour l’interface utilisateur, vous pouvez créer un nouveau CancellationTokenSource
pour chaque opération annulable qui n’est pas liée à un déclencheur d’annulation unique. Conservez une List
et ajoutez chaque source à la liste, en les disposant toutes lorsque votre composant est éliminé.
Pour les threads, créez un nouveau thread qui joint tous les threads de travail et ferme la source unique lorsque tous les threads de travail sont terminés. Voir CancellationTokenSource, Quand éliminer?
Il y a toujours un moyen. IDisposable
instances IDisposable
doivent toujours être éliminées. Les échantillons ne le font pas souvent parce qu’ils sont des échantillons rapides pour montrer l’utilisation principale ou parce que l’ajout de tous les aspects de la classe en cours de démonstration serait trop complexe pour un échantillon. L’échantillon est juste un échantillon, pas nécessairement (ou même généralement) un code de qualité de production. Tous les échantillons ne sont pas acceptables pour être copiés dans le code de production tels quels.
Cette réponse est encore à venir dans les recherches Google, et je pense que la réponse votée ne donne pas l’histoire complète. Après avoir examiné le code source pour CancellationTokenSource
(CTS) et CancellationToken
(CT), je pense que pour la plupart des cas d’utilisation, la séquence de code suivante convient:
if (cancelTokenSource != null) { cancelTokenSource.Cancel(); cancelTokenSource.Dispose(); cancelTokenSource = null; }
Le m_kernelHandle
interne m_kernelHandle
mentionné ci-dessus est l’object de synchronisation sauvegardant la propriété WaitHandle
dans les classes CTS et CT. Il n’est instancié que si vous accédez à cette propriété. Donc, à moins que vous n’utilisiez WaitHandle
pour certaines synchronisations de threads anciennes, votre appel de Task
n’aura aucun effet.
Bien sûr, si vous l’utilisez, vous devez faire ce qui est suggéré par les autres réponses ci-dessus et retarder l’appel Dispose
jusqu’à ce que toutes les opérations WaitHandle
utilisant le handle soient terminées car, comme décrit dans la documentation de l’API Windows pour WaitHandle , les résultats ne sont pas définis .
Créez une nouvelle application Windows Forms à partir du modèle de projet. Déposez un bouton sur le formulaire et double-cliquez dessus. Faites comme ceci:
private void button1_Click(object sender, EventArgs e) { var t = new System.Threading.Thread(() => { }); t.Start(); }
Appuyez sur Ctrl + F5 pour le démarrer. Démarrer + Exécuter, TaskMgr.exe, onglet Processus. Afficher + Sélectionner les colonnes et cocher “Poignées”. Observez la valeur de cette colonne pour le processus WindowsFormsApplication1.exe pendant que vous cliquez sur le bouton à plusieurs resockets.
La classe Thread n’a pas de méthode Dispose ().
Travaillons à partir de l’hypothèse qu’il en avait un. Quand l’appelleriez-vous?
En savoir plus sur la sagesse d’essayer d’éliminer des objects difficiles à éliminer dans cet article de Stephen Toub.
Cela fait longtemps que je ne l’ai pas demandé et j’ai reçu de nombreuses réponses utiles mais je suis tombé sur une question intéressante liée à cela et j’ai pensé que je la posterais ici comme une autre réponse:
Vous devez appeler CancellationTokenSource.Dispose()
uniquement lorsque vous êtes sûr que personne ne va essayer d’obtenir la propriété Token
de CTS. Sinon, vous ne devriez pas l’ appeler, car c’est une course. Par exemple, voir ici:
https://github.com/aspnet/AspNetKatana/issues/108
Dans le correctif pour ce problème, le code qui faisait précédemment cts.Cancel(); cts.Dispose();
cts.Cancel(); cts.Dispose();
a été édité pour ne faire que cts.Cancel();
car si quelqu’un de malchanceux tente d’obtenir le jeton d’annulation afin d’observer son état d’annulation après l’ appel de Dispose, il devra malheureusement aussi gérer ObjectDisposedException
– en plus de l’ OperationCanceledException
qu’ils prévoyaient.
Une autre observation clé liée à cette correction est faite par Tratcher: “La suppression est uniquement requirejse pour les jetons qui ne seront pas annulés, car l’annulation effectue tout le même nettoyage.” C’est-à-dire que le fait de simplement Cancel()
au lieu de s’en débarrasser est vraiment suffisant!