Quand passer le CancellationToken à Task.Run ?
La réponse courte : toujours au code du délégué, et idéalement aussi à Task.Run, mais ne croyez pas que ce second passage suffit à annuler le travail en cours.
Voici pourquoi et comment bien l’utiliser.
Le piège du CancellationToken dans Task.Run
Le CancellationToken passé à Task.Run n’agit que sur la planification du délégué sur le ThreadPool : si l’annulation est demandée avant que le délégué démarre (y compris pendant qu’il attend dans la file), il n’est pas exécuté et la tâche passe à l’état Canceled. Une fois que le délégué s’exécute (ce qui se produit presque immédiatement), ce token n’interrompt plus rien.
Beaucoup de développeurs écrivent ceci en pensant à tort que le délégué sera annulé :
| |
La bonne approche
Passez d’abord le token au code à l’intérieur du délégué : c’est ce qui permet réellement d’interrompre le travail.
| |
Cela rend explicite qu’il revient au code du délégué d’honorer l’annulation de l’opération. L’annulation est coopérative : rien ne tue le délégué de l’extérieur.
Passez ensuite le même token à Task.Run :
| |
Ce second passage a deux effets utiles :
- Si le token est déjà annulé avant le démarrage, le délégué n’est pas lancé.
- Si le délégué lève une
OperationCanceledExceptionportant le même token, la tâche passe àCanceled. Sans cela, le token ne correspond pas à celui de la tâche, qui passe àFaulted. Unawaitlève dans les deux cas uneOperationCanceledException, maisIsCanceled,IsFaultedet les continuations (OnlyOnCanceled, etc.) se comportent différemment.
Les trois usages du CancellationToken
Prenons l’exemple suivant :
| |
- Dans le délégué : c’est là que ça compte vraiment. Il arrête l’exécution de
DoSomething, à condition que la méthode soit écrite pour honorer les demandes d’annulation. - Dans
Task.Run: il annule la tâche si le token est annulé AVANT que le délégué commence (ce qui est rare), et il permet à la tâche de se terminer à l’étatCanceledplutôt queFaulted. - Dans
WaitAsync()(ouWait()) : il annule uniquement le processus d’attente de la tâche, sans réellement annuler la tâche elle-même.
Version de .NET
Task.WaitAsync(CancellationToken) n’existe que depuis .NET 6. Avec une version antérieure, utilisez Task.WhenAny(task, Task.Delay(Timeout.Infinite, token)) pour obtenir un effet comparable.
Attention si vous utilisez la version synchrone : task.Wait() enveloppe l’annulation de la tâche dans une AggregateException (contenant une TaskCanceledException). Seule l’annulation de l’attente via Wait(token) lève directement une OperationCanceledException. Un catch (OperationCanceledException) autour de Wait() peut donc rater l’annulation. Préférez await, qui extrait l’exception qu’on souhaite vraiment.
Comment implémenter l’annulation dans votre code
| |
Les bonnes pratiques
- Passez toujours
CancellationTokenaux méthodes asynchrones. - Vérifiez l’annulation dans les opérations longues.
- Utilisez
ThrowIfCancellationRequestedpour simplifier le code. - Créez le
CancellationTokenSourcelà où se prend la décision d’annuler (interface, délai, requête HTTP) et ne transmettez que leCancellationToken. - Disposez correctement de
CancellationTokenSource.
| |
Disposer correctement le CancellationTokenSource
CancellationTokenSource implémente IDisposable : il peut détenir un minuteur (après CancelAfter) et des enregistrements auprès d’autres tokens (sources liées). Ne pas le disposer retarde leur libération.
Cas 1
Une source locale, avec using :
| |
Cas 2
Une source liée à un token parent, à disposer elle aussi, sinon son enregistrement auprès du parent subsiste :
| |
Cas 3
Une source détenue par un objet de longue durée, qui devient IDisposable à son tour :
| |
Ne disposez jamais la source tant que des opérations utilisent encore son token : demander cts.Token après le Dispose lève une ObjectDisposedException. Annulez, attendez la fin des opérations, puis disposez.
Suivez-moi !
Merci d’avoir lu cet article. Assurez-vous de me suivre sur X, de vous abonner à ma publication Substack et d’ajouter mon blog à vos favoris pour ne pas manquer les prochains articles.
