Pourquoi CancellationToken est séparé de CancellationTokenSource?
Je suis à la recherche d'une justification de la raison .NET CancellationToken struct a été introduit en plus de la CancellationTokenSource classe. Je comprends comment l'API est utilisée, mais qui veulent aussi comprendre pourquoi il est conçu de cette façon.
I. e., pourquoi devons-nous:
var cts = new CancellationTokenSource();
SomeCancellableOperation(cts.Token);
...
public void SomeCancellableOperation(CancellationToken token) {
...
token.ThrowIfCancellationRequested();
...
}
au lieu de directement passer CancellationTokenSource autour comme:
var cts = new CancellationTokenSource();
SomeCancellableOperation(cts);
...
public void SomeCancellableOperation(CancellationTokenSource cts) {
...
cts.ThrowIfCancellationRequested();
...
}
Est-ce une optimisation de la performance basée sur le fait que l'annulation de contrôles d'état de se produire plus souvent que de passer le jeton autour?
De sorte que CancellationTokenSource pouvez garder une trace de et mise à jour CancellationTokens, et pour chaque jeton de l'annulation de la case est un champ local d'accès?
Étant donné qu'un volatile bool avec aucun verrouillage n'est suffisante dans les deux cas, je ne vois toujours pas pourquoi ce serait plus rapide.
Merci!
Vous devez vous connecter pour publier un commentaire.
J'ai été impliqué dans la conception et la mise en œuvre de ces classes.
La réponse courte est "la séparation des préoccupations". Il est vrai qu'il y a de diverses stratégies de mise en œuvre et que certaines sont plus simples au moins en ce qui concerne le type de système et d'apprentissage initiale. Cependant, la CTS et la TDM sont prévus pour une utilisation dans un grand nombre de scénarios (comme la profondeur de la bibliothèque de piles, calcul parallèle, async, etc) et donc a été conçu avec de nombreux complexes de cas d'utilisation à l'esprit. C'est un dessin destiné à encourager le succès de motifs et de décourager les anti-modèles sans sacrifier les performances.
Si la porte a été laissée ouverte pour virer les Api, alors l'utilité de l'annulation de la conception pourrait rapidement s'éroder.
J'ai eu exactement la même question et je voulais comprendre la logique derrière cette conception.
La accepté de répondre à eu la justification exactement. Voici la confirmation de la part de l'équipe qui a conçu cette fonction (l'emphase est mienne):
Lien: .NET 4 Annulation Cadre
À mon avis, le fait que CancellationToken ne peut que constater l'état et de ne pas la changer, est extrêmement critique. Vous pouvez remettre le jeton comme un bonbon et ne jamais s'inquiéter que quelqu'un d'autre que vous, vous l'Annuler. Il vous protège contre les hostiles de la troisième partie du code. Oui, les chances sont minces, mais personnellement, je voudrais que garantie.
J'ai aussi l'impression qu'il fait de l'API propre et évite accidentelle erreur et favorise une meilleure conception de composant.
Regardons API publique pour ces deux classes.
Si vous étiez à les combiner entre eux, lors de l'écriture de LongRunningFunction, je vais voir des méthodes semblables à celles de plusieurs surcharges de la "Annuler" qui je ne devrais pas être en utilisant. Personnellement, j'ai hate de voir la méthode dispose ainsi.
Je pense que la classe en cours de conception suit la fosse de la réussite de la philosophie, il guide les développeurs à créer de meilleurs composants qui peuvent gérer l'annulation de Tâches, puis instrument ensemble dans de nombreuses façon de créer des flux de travail complexes.
Permettez-moi de vous poser une question, avez-vous demandé quel est le but de jeton.Inscrire? Elle n'a pas de sens pour moi. Et puis j'ai lu Annulation dans la gestion des Threads et tout est devenu limpide.
Je crois que l'Annulation Cadre de la Conception dans le TPL est absolument parfait.
CancellationTokenSource
peut réellement lancer la demande d'annulation de il est associé à jeton (token ne peut pas le faire par lui-même): Le CancellationToken a cette interne constructeur:internal CancellationToken(CancellationTokenSource source) { this.m_source = source; }
et cette propriété:public bool IsCancellationRequested { get { return this.m_source != null && this.m_source.IsCancellationRequested; } }
La CancellationTokenSource utilise l'interne constructeur, de sorte que le jeton a la référence de la source (m_source)Ils sont séparés non pas pour des raisons techniques, mais sémantique ceux. Si vous regardez à la mise en œuvre de
CancellationToken
sous ILSpy, vous trouverez que c'est simplement un wrapper autour deCancellationTokenSource
(et donc pas différents en terme de performance que de passer autour d'une référence).Ils fournissent à cette séparation de la fonctionnalité pour rendre les choses plus prévisible: quand vous passez une méthode, d'une
CancellationToken
, vous savez que vous êtes toujours le seul qui peut l'annuler. Bien sûr, la méthode pourrait toujours jeter unTaskCancelledException
, mais leCancellationToken
elle-même, et toutes les autres méthodes de référencement, même -- serait de rester en sécurité.CancellationTokenSource
. Vous pensez que vous pourriez simplement dire "ne faites pas cela", mais les gens (moi y compris!) l'occasion de faire ces choses de toute façon de l'obtenir à un certain caché de la fonctionnalité, et qu'il faudrait arriver. C'est ma théorie actuelle, au moins.Task.Run()
de l'aide, le fil (comme une fermeture) a accès à la fois à la CTS et la CT; il n'y a pas de protection. Et de fournir le jeton via leTask.Run(Action, Token)
n'a pas de pass le jeton dans le thread, je veux dire, ne serait-il pas plus de sens que la signature soitTask.Run(Action<Token>, Token)
?Le CancellationToken est une structure autant d'exemplaires pourraient exister grâce à en passant le long de méthodes.
La CancellationTokenSource définit l'état de TOUTES les copies d'un jeton lors de l'appel d'Annuler la source. Voir cette page MSDN
La raison de la conception pourrait être simplement une question de séparation des préoccupations et de la vitesse d'un struct.
La CancellationTokenSource est la "chose" que les questions de l'annulation, pour quelque raison que ce soit. Il a besoin d'une façon "d'expédition" que l'annulation de tous les CancellationToken est qu'il a émises. C'est ainsi que, par exemple, ASP.NET peut annuler les opérations lorsqu'une demande est annulée. Chaque demande a un CTSource qui transmet l'annulation de tous les jetons qu'il a émises.
Cette grande pour les tests unitaires BTW - créez votre propre source de jeton d'annulation, obtenir un jeton, d'appel d'Annuler le soource, et passe le jeton à votre code pour gérer l'annulation.
Ma "théorie", c'est que le rôle de CancellationToken est de fournir un "thread-safe" wrapper pour éviter contention.
Si il y avait seulement une classe, une instance unique serait utilisé avec la copie de référence, et vous avez beaucoup de discussions enregistrement des gestionnaires d'annulation par exemple, cette classe doit gérer la simultanéité à l'aide de certains de verrouillage, la réduction de la performance.
Alors qu'avec le CancellationToken struct vous avez une copie de chaque rappels-la file d'attente pour chaque thread, donc pas de conflit.
C'est de la pure spéculation et j'ai peut-être complètement faux et/ou il peut y avoir d'autres raisons sont bonnes pour cette conception.
La seule chose qui est certaine, c'est qu'il y a au moins une bonne raison, et c'est une honte ce genre de décision n'est pas plus documenté, comme il est un aperçu inestimable lors de l'utilisation d'une technologie.
CancellationToken
fondamentalement, il suffit d'appeler des méthodes surCancellationTokenSource
, il n'y a pas une file d'attente de rappels pour chaqueCancellationToken
. Et votre argument sur les conflits n'a pas beaucoup de sens de toute façon.