La classe CancellationTokenSource
est jetable. Un rapide coup d’œil dans Reflector prouve l’utilisation de KernelEvent
, une ressource (très probablement) non gérée. Puisque CancellationTokenSource
ne possède pas de finaliseur, si nous ne le supprimons pas, le GC ne le fera pas.
En revanche, si vous consultez les exemples répertoriés dans l'article MSDN Annulation dans les threads gérés , un seul extrait de code dispose du jeton.
Quelle est la bonne façon de l'éliminer dans le code?
using
si vous ne l'attendez pas. Et il est logique d’annuler uniquement si vous n’attendez pas.ContinueWith
à la tâche avec un appel Dispose
, mais est-ce la solution?.ForAll(x => Console.Write(x))
?Comme il n’a pas quelque chose de semblable à une méthode Reset
pour nettoyer les champs IsCancelRequested
et Token
, je suppose qu’il n’est pas réutilisable. Ainsi, chaque fois que vous démarrez une tâche (ou PLINQ) vous devez en créer une nouvelle. Est-ce vrai? Si oui, ma question est quelle est la stratégie correcte et recommandée pour traiter Dispose
sur ces nombreuses instances CancellationTokenSource
?
Pour 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 CancellationTokenSource
était le problème.
Mon projet propose un service qui lit constamment la base de données et exécute différentes tâches. Je transmettais des jetons d'annulation liés à mes employés. Ainsi, même après la fin du traitement des données, les jetons d'annulation n'étaient pas supprimés, ce qui provoquait une fuite de mémoire.
MSDN Annulation dans les threads gérés indique clairement:
Notez que vous devez appeler
Dispose
sur la source du jeton lié lorsque vous avez terminé. Pour un exemple plus complet, voir Comment: écouter plusieurs demandes d'annulation .
J'ai utilisé ContinueWith
dans mon implémentation.
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 servait à deux fins principales. Si WaitHandle de CancellationToken avait été utilisé (en l'attribuant par conséquent), Dispose en disposera. 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 objectif supplémentaire: si le CTS utilise une minuterie sous les capots (par exemple, CancelAfter a été appelé), la minuterie sera mise au rebut.
Il est très rare que CancellationToken.WaitHandle soit utilisé, aussi, nettoyer après cela 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 minuterie de CTS, l'utilisation de Dispose peut avoir plus d'impact.
Je pense que la partie audacieuse est la partie importante. Il utilise "plus percutant" ce qui le rend un peu vague. J'interprète cela comme voulant dire qu'il faut appeler Dispose
dans ces situations-là, sinon utiliser Dispose
n'est pas nécessaire.
J'ai jeté un œil dans ILSpy pour le CancellationTokenSource
, mais je ne trouve que m_KernelEvent qui est en fait un ManualResetEvent
, qui est une classe wrapper pour un objet WaitHandle. Cela devrait être géré correctement par le GC.
Vous devriez toujours disposer CancellationTokenSource
.
Comment en disposer dépend exactement du scénario. Vous proposez plusieurs scénarios différents.
using
ne fonctionne que 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
que vous avez indiquée pour éliminer CancellationTokenSource
.
Pour plinq, vous pouvez utiliser using
puisque vous l'exécutez en parallèle mais attendez que tous les travailleurs exécutant parallèlement soient terminés.
Pour l'interface utilisateur, vous pouvez créer un nouveau CancellationTokenSource
pour chaque opération pouvant être annulée qui n'est pas liée à un seul déclencheur d'annulation. Maintenir un List<IDisposable>
et ajoutez chaque source à la liste, en les supprimant lorsque votre composant est supprimé.
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 en disposer?
Il y a toujours un moyen. IDisposable
instances doivent toujours être supprimées. Les échantillons ne le font pas souvent parce qu'ils sont soit des échantillons rapides pour montrer l'utilisation du coeur, soit parce que l'ajout de tous les aspects de la classe démontrée serait trop complexe pour un échantillon. L’échantillon n’est qu’un échantillon, pas nécessairement (ni même habituellement) un code de qualité de la production. Tous les échantillons ne peuvent pas être copiés tels quels dans le code de production.
Cette réponse est encore à venir dans les recherches Google, et je crois que la réponse qui a été votée est qu'elle ne donne pas toute l'histoire. Après avoir examiné code source pour CancellationTokenSource
(CTS) et CancellationToken
(CT), je pense que dans la plupart des cas, la séquence de code suivante convient:
if (cancelTokenSource != null)
{
cancelTokenSource.Cancel();
cancelTokenSource.Dispose();
cancelTokenSource = null;
}
Le m_kernelHandle
_ Le champ interne mentionné ci-dessus est l’objet 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 utilisiez WaitHandle
pour une synchronisation de threads old-school dans votre appel Task
, la disposition n'aura aucun effet.
Bien sûr, si vous l'utilisez , vous devriez faire ce que suggèrent les réponses précédentes et retarder l'appel de Dispose
jusqu'à ce que WaitHandle
les opérations utilisant le handle sont terminées, car, comme décrit dans la documentation de l'API Windows pour WaitHandle , les résultats ne sont pas définis.
Cela fait longtemps que je n'ai pas posé cette question et obtenu de nombreuses réponses utiles, mais je suis tombé sur un problème intéressant et j'ai pensé le poster ici comme une autre réponse:
Vous ne devez appeler CancellationTokenSource.Dispose()
que lorsque vous êtes certain que personne ne tentera d'obtenir la propriété Token
de la CTS. Sinon, vous devriez ne pas l'appeler , car c'est une course. Par exemple, voir ici:
https://github.com/aspnet/AspNetKatana/issues/108
Dans le correctif de ce problème, le code précédemment utilisé par cts.Cancel(); cts.Dispose();
a été modifié pour ne faire que cts.Cancel();
car quiconque ayant la malchance d'essayer d'obtenir le jeton d'annulation afin de respecter son état d'annulation après Dispose a été appelé, mais devra aussi gérer ObjectDisposedException
- en plus du OperationCanceledException
pour lequel il était prévu. .
Tratcher fait une autre observation clé liée à ce correctif: "L'élimination n'est requise que pour les jetons qui ne seront pas annulés, car l'annulation effectue le même nettoyage." c'est-à-dire qu'il suffit de faire Cancel()
au lieu de disposer, c'est vraiment bien!