OK, mes questions sont vraiment simples. Pourquoi ce code ne jette pas TaskCancelledException
?
static void Main()
{
var v = Task.Run(() =>
{
Thread.Sleep(1000);
return 10;
}, new CancellationTokenSource(500).Token).Result;
Console.WriteLine(v); // this outputs 10 - instead of throwing error.
Console.Read();
}
Mais celui-ci fonctionne
static void Main()
{
var v = Task.Run(() =>
{
Thread.Sleep(1000);
return 10;
}, new CancellationToken(true).Token).Result;
Console.WriteLine(v); // this one throws
Console.Read();
}
Annulation dans les fils gérés :
L'annulation est coopérative et n'est pas imposée à l'auditeur. Le programme d'écoute détermine comment terminer en douceur en réponse à une demande d'annulation.
Vous n'avez écrit aucun code dans votre méthode Task.Run
pour accéder à votre CancellationToken
et pour implémenter une annulation - de sorte que vous avez effectivement ignoré la demande d'annulation et êtes allé jusqu'au bout.
Il y a une différence dans l'annulation d'une tâche en cours et une tâche dont l'exécution est planifiée.
Après l'appel de la méthode Task.Run, la tâche est uniquement planifiée et n'a probablement pas encore été exécutée.
Lorsque vous utilisez la famille de surcharges avec prise en charge de l'annulation Task.Run (..., CancellationToken), le jeton d'annulation est vérifié lorsque la tâche est sur le point de s'exécuter. Si IsCancellationRequested est défini sur true pour le jeton d'annulation, une exception du type TaskCanceledException est levée.
Si la tâche est déjà en cours d'exécution, il incombe à la tâche d'appeler la méthode ThrowIfCancellationRequested ou de simplement lever l'opération OperationCanceledException.
Selon MSDN, il ne s'agit que d'une méthode pratique pour les éléments suivants:
if (jeton.IsCancellationRequested) lancer une nouvelle OperationCanceledException (jeton);
Ce n'est pas le type d'exception utilisé dans ces deux cas:
catch (TaskCanceledException ex)
{
// Task was canceled before running.
}
catch (OperationCanceledException ex)
{
// Task was canceled while running.
}
Notez également que TaskCanceledException
dérive de OperationCanceledException
. Vous ne pouvez donc avoir qu'une seule clause catch
pour le type OperationCanceledException
:
catch (OperationCanceledException ex)
{
if (ex is TaskCanceledException)
// Task was canceled before running.
// Task was canceled while running.
}
Je pense que parce que vous n'invoquez pas la méthode ThrowIfCancellationRequested()
à partir de votre objet CancellationToken ..__, vous ignorez ainsi la demande d'annulation de la tâche.
Vous devriez faire quelque chose comme ça:
void Main()
{
var ct = new CancellationTokenSource(500).Token;
var v =
Task.Run(() =>
{
Thread.Sleep(1000);
ct.ThrowIfCancellationRequested();
return 10;
}, ct).Result;
Console.WriteLine(v); //now a TaskCanceledException is thrown.
Console.Read();
}
La deuxième variante de votre code fonctionne, car vous êtes déjà en train d'initialiser un jeton avec un état Canceled
défini sur true. En effet, comme indiqué ici :
If canceled is true, both CanBeCanceled and IsCancellationRequested will be true
l'annulation a déjà été demandée et l'exception TaskCanceledException
sera immédiatement levée, sans démarrer la tâche.
Une autre implémentation utilisant Task.Delay avec token à la place est Thread.Sleep.
static void Main(string[] args)
{
var task = GetValueWithTimeout(1000);
Console.WriteLine(task.Result);
Console.ReadLine();
}
static async Task<int> GetValueWithTimeout(int milliseconds)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.CancelAfter(milliseconds);
token.ThrowIfCancellationRequested();
var workerTask = Task.Run(async () =>
{
await Task.Delay(3500, token);
return 10;
}, token);
try
{
return await workerTask;
}
catch (OperationCanceledException )
{
return 0;
}
}