Étant donné le code suivant:
var cts = new CancellationTokenSource();
try
{
// get a "hot" task
var task = new HttpClient().GetAsync("http://www.google.com", cts.Token);
// request cancellation
cts.Cancel();
await task;
// pass:
Assert.Fail("expected TaskCanceledException to be thrown");
}
catch (TaskCanceledException ex)
{
// pass:
Assert.IsTrue(cts.Token.IsCancellationRequested,
"expected cancellation requested on original token");
// fail:
Assert.IsTrue(ex.CancellationToken.IsCancellationRequested,
"expected cancellation requested on token attached to exception");
}
Je m'attendrais à ex.CancellationToken.IsCancellationRequested
être true
à l'intérieur du bloc catch, mais ce n'est pas le cas. Suis-je en train de mal comprendre quelque chose?
C'est le cas parce que HttpClient
en interne (dans SendAsync
) utilise un TaskCompletionSource
pour représenter l'opération async
. Il renvoie TaskCompletionSource.Task
Et c'est la tâche sur laquelle vous await
.
Il appelle ensuite base.SendAsync
Et enregistre une continuation sur la tâche retournée qui annule/termine/échoue la tâche de TaskCompletionSource
en conséquence.
Dans le cas d'une annulation, il utilise TaskCompletionSource.TrySetCanceled
qui associe la tâche annulée à un nouveau CancellationToken
(default(CancellationToken)
).
Vous pouvez le voir en regardant le TaskCanceledException
. En plus de ex.CancellationToken.IsCancellationRequested
Étant false
ex.CancellationToken.CanBeCanceled
Est également false
, ce qui signifie que ce CancellationToken
ne peut jamais être annulé car il n'a pas été créé en utilisant un CancellationTokenSource
.
OMI, il devrait utiliser TaskCompletionSource.TrySetCanceled(CancellationToken)
à la place. De cette façon, le TaskCompletionSource
sera associé au CancellationToken
transmis par le consommateur et pas simplement le CancellationToken
par défaut. Je pense que c'est un bug (bien que mineur) et j'ai soumis un problème lors de la connexion à ce sujet.
@Bengie Cela n'a pas fonctionné pour moi. J'ai dû le modifier un peu. IsCancellationRequested a toujours renvoyé true, donc je ne pouvais pas compter sur cela.
Cela a fonctionné pour moi:
using (CancellationTokenSource cancelAfterDelay = new CancellationTokenSource(TimeSpan.FromSeconds(timeout)))
{
DateTime startedTime = DateTime.Now;
try
{
response = await request.ExecuteAsync(cancelAfterDelay.Token);
}
catch (TaskCanceledException e)
{
DateTime cancelledTime = DateTime.Now;
if (startedTime.AddSeconds(timeout-1) <= cancelledTime)
{
throw new TimeoutException($"An HTTP request to {request.Url} timed out ({timeout} seconds)");
}
else
throw;
}
}
return response;
J'ai défini le délai d'infini pour le désactiver, puis je passe mon propre jeton d'annulation.
using(CancellationTokenSource cancelAfterDelay = new CancellationTokenSource(timespan/timeout))
...
catch(OperationCanceledException e)
{
if(!cancelAfterDelay.Token.IsCancellationRequested)
throw new TimeoutException($"An HTTP request to {request.Uri} timed out ({(int)requestTimeout.TotalSeconds} seconds)");
else
throw;
}