D'après ce que j'ai lu sur les tâches, le code suivant devrait annuler la tâche en cours d'exécution sans lever d'exception. J'avais l'impression que le but de l'annulation d'une tâche était de "demander" poliment à la tâche de s'arrêter sans interrompre les discussions.
La sortie du programme suivant est:
Exception de dumping
[OperationCanceledException]
Annulation et retour du dernier prime calculé.
J'essaie d'éviter toute exception lors de l'annulation. Comment puis-je accomplir cela?
void Main()
{
var cancellationToken = new CancellationTokenSource();
var task = new Task<int>(() => {
return CalculatePrime(cancellationToken.Token, 10000);
}, cancellationToken.Token);
try
{
task.Start();
Thread.Sleep(100);
cancellationToken.Cancel();
task.Wait(cancellationToken.Token);
}
catch (Exception e)
{
Console.WriteLine("Dumping exception");
e.Dump();
}
}
int CalculatePrime(CancellationToken cancelToken, object digits)
{
int factor;
int lastPrime = 0;
int c = (int)digits;
for (int num = 2; num < c; num++)
{
bool isprime = true;
factor = 0;
if (cancelToken.IsCancellationRequested)
{
Console.WriteLine ("Cancelling and returning last calculated prime.");
//cancelToken.ThrowIfCancellationRequested();
return lastPrime;
}
// see if num is evenly divisible
for (int i = 2; i <= num/2; i++)
{
if ((num % i) == 0)
{
// num is evenly divisible -- not prime
isprime = false;
factor = i;
}
}
if (isprime)
{
lastPrime = num;
}
}
return lastPrime;
}
Vous jetez explicitement une exception sur cette ligne:
cancelToken.ThrowIfCancellationRequested();
Si vous souhaitez quitter gracieusement la tâche, il vous suffit de vous débarrasser de cette ligne.
Généralement, les gens l'utilisent comme mécanisme de contrôle pour s'assurer que le traitement actuel est abandonné sans potentiellement exécuter de code supplémentaire. De plus, il n'est pas nécessaire de vérifier l'annulation lors de l'appel de ThrowIfCancellationRequested()
car il est fonctionnellement équivalent à:
if (token.IsCancellationRequested)
throw new OperationCanceledException(token);
Lorsque vous utilisez ThrowIfCancellationRequested()
votre tâche pourrait ressembler à ceci:
int CalculatePrime(CancellationToken cancelToken, object digits) {
try{
while(true){
cancelToken.ThrowIfCancellationRequested();
//Long operation here...
}
}
finally{
//Do some cleanup
}
}
De plus, Task.Wait(CancellationToken)
lèvera une exception si le jeton a été annulé. Pour utiliser cette méthode, vous devrez encapsuler votre appel Wait dans un bloc Try...Catch
.
J'essaie d'éviter toute exception lors de l'annulation.
Tu ne devrais pas faire ça.
Lancer OperationCanceledException
est la façon idiomatique dont "la méthode que vous avez appelée a été annulée" est exprimée en TPL. Ne vous battez pas contre cela - attendez-vous à cela.
C'est une bonne chose , car cela signifie que lorsque vous avez plusieurs opérations utilisant le même jeton d'annulation, vous n'avez pas besoin de poivrer votre code à chaque niveau avec des vérifications pour voir si la méthode que vous venez d'appeler s'est terminée correctement ou si elle est retournée en raison d'une annulation. Vous pourriez utiliser CancellationToken.IsCancellationRequested
partout, mais cela rendra votre code beaucoup moins élégant à long terme.
Notez qu'il y a deux morceaux de code dans votre exemple qui lèvent une exception - une dans la tâche elle-même:
cancelToken.ThrowIfCancellationRequested()
et celui où vous attendez la fin de la tâche:
task.Wait(cancellationToken.Token);
Je ne pense pas que vous souhaitiez vraiment passer le jeton d'annulation dans le task.Wait
appelez, pour être honnête ... qui permet autre code d'annuler votre attente . Étant donné que vous savez que vous venez d'annuler ce jeton, il est inutile - il est lié de lever une exception, que la tâche ait déjà remarqué l'annulation ou ne pas. Options:
Certaines des réponses ci-dessus se lisent comme si ThrowIfCancellationRequested()
serait une option. Il s'agit de pas dans ce cas, car vous n'obtiendrez pas le dernier premier résultat obtenu. Le idiomatic way that "the method you called was cancelled"
est défini pour les cas où l'annulation signifie la suppression des résultats (intermédiaires). Si votre définition de l'annulation est "arrêtez le calcul et retournez le dernier résultat intermédiaire", vous êtes déjà parti de cette façon.
Discuter des avantages en particulier en termes d'exécution est également assez trompeur: l'algorithme implémenté est nul à l'exécution. Même une annulation hautement optimisée ne fera aucun bien.
L'optimisation la plus simple serait de dérouler cette boucle et de sauter des cycles inutiles:
for(i=2; i <= num/2; i++) {
if((num % i) == 0) {
// num is evenly divisible -- not prime
isprime = false;
factor = i;
}
}
Vous pouvez
Cela revient à économiser un minimum garanti de 75% (estimation approximative: 90%) de cycles dans la boucle intérieure, simplement en la remplaçant par:
if ((num % 2) == 0) {
isprime = false;
factor = 2;
} else {
for(i=3; i <= (int)Math.sqrt(num); i+=2) {
if((num % i) == 0) {
// num is evenly divisible -- not prime
isprime = false;
factor = i;
break;
}
}
}
Il existe des algorithmes beaucoup plus rapides (dont je ne parlerai pas parce que je suis assez loin du sujet) mais cette optimisation est assez facile et prouve toujours mon point: ne vous inquiétez pas de la micro-optimisation de l'exécution lorsque votre algorithme est this loin d'être optimal.
Une autre remarque sur les avantages d'utiliser ThrowIfCancellationRequested
plutôt que IsCancellationRequested
: J'ai trouvé que lorsque vous devez utiliser ContinueWith
avec une option de continuation de TaskContinuationOptions.OnlyOnCanceled
, IsCancellationRequested
ne provoquera pas le déclenchement du ContinueWith
conditionné. ThrowIfCancellationRequested
, cependant, will définit la condition Annulée de la tâche, provoquant le déclenchement de ContinueWith
.
Remarque: cela n'est vrai que lorsque la tâche est déjà en cours d'exécution et non lorsque la tâche démarre. C'est pourquoi j'ai ajouté une Thread.Sleep()
entre le début et l'annulation.
CancellationTokenSource cts = new CancellationTokenSource();
Task task1 = new Task(() => {
while(true){
if(cts.Token.IsCancellationRequested)
break;
}
}, cts.Token);
task1.ContinueWith((ant) => {
// Perform task1 post-cancellation logic.
// This will NOT fire when calling cst.Cancel().
}
Task task2 = new Task(() => {
while(true){
cts.Token.ThrowIfCancellationRequested();
}
}, cts.Token);
task2.ContinueWith((ant) => {
// Perform task2 post-cancellation logic.
// This will fire when calling cst.Cancel().
}
task1.Start();
task2.Start();
Thread.Sleep(3000);
cts.Cancel();