La classe CancellationTokenSource
est jetable. Un rapide coup d'œil dans Reflector prouve l'utilisation d' KernelEvent
une ressource (très probablement) non gérée. Puisque CancellationTokenSource
n'a pas de finaliseur, si nous ne le supprimons pas, le GC ne le fera pas.
En revanche, si vous examinez les exemples répertoriés dans l'article MSDN Annulation dans les threads gérés , un seul extrait de code supprime le jeton.
Quelle est la bonne façon de s'en débarrasser dans le code?
- Vous ne pouvez pas encapsuler le code commençant votre tâche parallèle avec
using
si vous ne l'attendez pas. Et il est logique d'avoir une annulation uniquement si vous n'attendez pas. - Bien sûr, vous pouvez ajouter une
ContinueWith
tâche avec unDispose
appel, mais est-ce la voie à suivre? - Qu'en est-il des requêtes PLINQ annulables, qui ne se synchronisent pas, mais font simplement quelque chose à la fin? Disons
.ForAll(x => Console.Write(x))
? - Est-ce réutilisable? Le même jeton peut-il être utilisé pour plusieurs appels, puis le disposer avec le composant hôte, disons le contrôle de l'interface utilisateur?
Parce qu'il n'a pas quelque chose comme une Reset
méthode de nettoyage IsCancelRequested
et de Token
champ, je suppose qu'il n'est pas réutilisable, donc chaque fois que vous démarrez une tâche (ou une requête PLINQ), vous devez en créer une nouvelle. Est-ce vrai? Si oui, ma question est de savoir quelle est la stratégie correcte et recommandée pour traiter Dispose
ces nombreux CancellationTokenSource
cas?
la source
Important: The CancellationTokenSource class implements the IDisposable interface. You should be sure to call the CancellationTokenSource.Dispose method when you have finished using the cancellation token source to free any unmanaged resources it holds.
- docs.microsoft.com/en-us/dotnet/standard/threadingJe ne pense pas qu'aucune des réponses actuelles ne soit satisfaisante. Après des recherches, j'ai trouvé cette réponse de Stephen Toub ( référence ):
La partie audacieuse, je pense, est la partie importante. Il utilise «plus percutant» ce qui laisse un peu flou. Je l'interprète comme signifiant que l'appel
Dispose
dans ces situations doit être fait, sinon l'utilisationDispose
n'est pas nécessaire.la source
J'ai jeté un coup d'œil dans ILSpy pour le
CancellationTokenSource
mais je ne peux trouver quem_KernelEvent
ce qui est en fait uneManualResetEvent
, qui est une classe wrapper pour unWaitHandle
objet. Cela doit être géré correctement par le GC.la source
Vous devez toujours vous débarrasser
CancellationTokenSource
.La manière de s'en débarrasser dépend exactement du scénario. Vous proposez plusieurs scénarios différents.
using
ne fonctionne que lorsque vous utilisezCancellationTokenSource
un travail parallèle que vous attendez. Si c'est votre senario, alors tant mieux, c'est la méthode la plus simple.Lorsque vous utilisez des tâches, utilisez une
ContinueWith
tâche comme vous l'avez indiqué pour vous en débarrasserCancellationTokenSource
.Pour plinq, vous pouvez l'utiliser
using
car vous l'exécutez en parallèle mais attendez que tous les nœuds de calcul exécutés en parallèle aient terminé.Pour l'interface utilisateur, vous pouvez créer un nouveau
CancellationTokenSource
pour chaque opération annulable qui n'est pas liée à un seul déclencheur d'annulation. GérezList<IDisposable>
et ajoutez chaque source à la liste, en les éliminant toutes 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
les instances doivent toujours être supprimées. Les échantillons ne le font souvent pas parce qu'ils sont soit des échantillons rapides pour montrer l'utilisation du noyau, 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 (ou même généralement) un code de qualité de production. Tous les échantillons ne peuvent pas être copiés tels quels dans le code de production.la source
await
sur la tâche et disposer le CancellationTokenSource dans le code qui vient après l'attente?await
une opération, vous pouvez reprendre en raison d'unOperationCanceledException
. Vous pourriez alors appelerDispose()
. Mais s'il y a des opérations toujours en cours d'exécution et utilisant le correspondantCancellationToken
, ce jeton signale toujoursCanBeCanceled
comme étanttrue
même si la source est supprimée. S'ils tentent d'enregistrer un rappel d'annulation, BOOM! ,ObjectDisposedException
. Il est suffisamment sûr d'appelerDispose()
après la réussite des opérations. Cela devient vraiment délicat lorsque vous devez annuler quelque chose.Cette réponse est toujours à venir dans les recherches Google, et je pense que la réponse votée ne donne pas toute l'histoire. Après avoir examiné le code source pour
CancellationTokenSource
(CTS) etCancellationToken
(CT), je pense que pour la plupart des cas d'utilisation, la séquence de code suivante convient:Le
m_kernelHandle
champ interne mentionné ci-dessus est l'objet de synchronisation qui soutient laWaitHandle
propriété dans les classes CTS et CT. Il n'est instancié que si vous accédez à cette propriété. Donc, à moins que vous n'utilisiezWaitHandle
pour une synchronisation de thread à l'ancienne dans votreTask
appel, disposer n'aura aucun effet.Bien sûr, si vous êtes l' utilisez , vous devez faire ce qui est suggéré par les autres réponses ci - dessus et les appels de retard
Dispose
jusqu'à ce que toutes lesWaitHandle
opérations à l' aide de la poignée sont complètes, parce que, comme cela est décrit dans la documentation de l' API Windows pour WaitHandle , les résultats ne sont pas définis.la source
IsCancellationRequested
propriété du jeton en interrogeant, en rappelant ou en attendant la poignée.» En d'autres termes: ce n'est peut-être pas vous (c'est-à-dire celui qui fait la requête asynchrone) qui utilise le handle d'attente, ce peut être l'auditeur (c'est-à-dire celui qui répond à la requête). Ce qui signifie que vous, en tant que responsable de l'élimination, n'avez aucun contrôle sur l'utilisation ou non de la poignée d'attente.Cela fait longtemps que je n'ai pas posé cette question et que j'ai obtenu de nombreuses réponses utiles, mais je suis tombé sur un problème intéressant lié à cela et j'ai pensé que je le publierais ici comme une autre réponse:
Vous ne devez appeler
CancellationTokenSource.Dispose()
que lorsque vous êtes sûr que personne ne tentera d'obtenir laToken
propriété du 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 de ce problème, le code qui auparavant a
cts.Cancel(); cts.Dispose();
été modifié pour le faire simplementcts.Cancel();
parce que toute personne ayant la malchance d'essayer d'obtenir le jeton d'annulation afin d'observer son état d'annulation aprèsDispose
avoir été appelé devra malheureusement également gérerObjectDisposedException
- en plus duOperationCanceledException
qu'ils prévoyaient.Une autre observation clé liée à ce correctif est faite par Tratcher: "La suppression 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 se débarrasser!la source
J'ai créé une classe thread-safe qui lie a
CancellationTokenSource
à aTask
et garantit que leCancellationTokenSource
sera supprimé une fois son associéTask
terminé. Il utilise des verrous pour garantir que leCancellationTokenSource
ne sera pas annulé pendant ou après sa mise au rebut. Cela se produit pour la conformité à la documentation , qui stipule:Et aussi :
Voici la classe:
Les principales méthodes de la
CancelableExecution
classe sont lesRunAsync
et lesCancel
. Par défaut, les opérations simultanées ne sont pas autorisées, ce qui signifie que l'appelRunAsync
deuxième annulera silencieusement et attendra la fin de l'opération précédente (si elle est toujours en cours), avant de démarrer la nouvelle opération.Cette classe peut être utilisée dans des applications de tout type. Son utilisation principale est cependant dans les applications d'interface utilisateur, dans des formulaires avec des boutons pour démarrer et annuler une opération asynchrone, ou avec une zone de liste qui annule et redémarre une opération à chaque fois que son élément sélectionné est modifié. Voici un exemple du premier cas:
La
RunAsync
méthode accepte un extraCancellationToken
comme argument, qui est lié au créé en interneCancellationTokenSource
. Fournir ce jeton facultatif peut être utile dans les scénarios avancés.la source