Quelle est la meilleure façon de dormir un certain temps, mais pouvoir être interrompu par un IsCancellationRequested
d'un CancellationToken
?
Je recherche une solution qui fonctionne dans .NET 4.0.
J'aimerais écrire
void MyFunc (CancellationToken ct)
{
//...
// simulate some long lasting operation that should be cancelable
Thread.Sleep(TimeSpan.FromMilliseconds(10000), ct);
}
Je viens de bloguer à ce sujet ici:
CancellationToken et Thread.Sleep
en bref:
var cancelled = token.WaitHandle.WaitOne(TimeSpan.FromSeconds(5));
Dans votre contexte:
void MyFunc (CancellationToken ct)
{
//...
// simulate some long lasting operation that should be cancelable
var cancelled = ct.WaitHandle.WaitOne(TimeSpan.FromSeconds(10));
}
Alternativement, je pense que c'est assez clair:
Task.Delay(waitTimeInMs, cancellationToken).Wait(cancellationToken);
Pour annuler une opération asynchrone après un certain temps tout en étant en mesure d'annuler l'opération manuellement, utilisez quelque chose comme ce qui suit
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.CancelAfter(5000);
Cela entraînera une annulation après cinq secondes. Pour annuler l'opération vous-même, tout ce que vous avez à faire est de passer le token
dans votre méthode asynchrone et d'utiliser la méthode token.ThrowifCancellationRequested()
, où vous avez configuré un gestionnaire d'événements quelque part pour déclencher la fonction cts.Cancel()
.
Un exemple complet est donc:
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.CancelAfter(5000);
// Set up the event handler on some button.
if (cancelSource != null)
{
cancelHandler = delegate
{
Cancel(cts);
};
stopButton.Click -= cancelHandler;
stopButton.Click += cancelHandler;
}
// Now launch the method.
SomeMethodAsync(token);
Où stopButton
est le bouton sur lequel vous cliquez pour annuler la tâche en cours
private void Cancel(CancellationTokenSource cts)
{
cts.Cancel();
}
et la méthode est définie comme
SomeMethodAsync(CancellationToken token)
{
Task t = Task.Factory.StartNew(() =>
{
msTimeout = 5000;
Pump(token);
}, token,
TaskCreationOptions.None,
TaskScheduler.Default);
}
Maintenant, pour vous permettre de travailler le fil mais également activer l'annulation de l'utilisateur, vous devrez écrire une méthode de "pompage"
int msTimeout;
bool timeLimitReached = false;
private void Pump(CancellationToken token)
{
DateTime now = DateTime.Now;
System.Timer t = new System.Timer(100);
t.Elapsed -= t_Elapsed;
t.Elapsed += t_Elapsed;
t.Start();
while(!timeLimitReached)
{
Thread.Sleep(250);
token.ThrowIfCancellationRequested();
}
}
void t_Elapsed(object sender, ElapsedEventArgs e)
{
TimeSpan elapsed = DateTime.Now - this.readyUpInitialised;
if (elapsed > msTimeout)
{
timeLimitReached = true;
t.Stop();
t.Dispose();
}
}
Notez que SomeAsyncMethod
retournera directement à l'appelant. Pour bloquer également l'appelant, vous devrez déplacer le Task
vers le haut dans la hiérarchie des appels.
La meilleure solution que j'ai trouvée jusqu'à présent est:
void MyFunc(CancellationToken ct)
{
//...
var timedOut = WaitHandle.WaitAny(new[] { ct.WaitHandle }, TimeSpan.FromMilliseconds(2000)) == WaitHandle.WaitTimeout;
var cancelled = ! timedOut;
}
MISE À JOUR:
La meilleure solution jusqu'à présent est la réponse acceptée .
Le CancellationToken.WaitHandle peut lever une exception lors de l'accès après que le CancellationTokenSource a été supprimé:
ObjectDisposedException: le CancellationTokenSource a été supprimé.
Dans certains cas, en particulier lorsque sources d'annulation liées sont éliminées manuellement (comme il se doit), cela peut être gênant.
Cette méthode d'extension permet une "attente d'annulation en toute sécurité"; cependant, il doit être utilisé en conjonction avec les vérifications et l'indication appropriée de l'état du jeton d'annulation et/ou de l'utilisation de la valeur de retour. En effet, il supprime les exceptions d'accès au WaitHandle et peut retourner plus rapidement que prévu.
internal static class CancellationTokenExtensions
{
/// <summary>
/// Wait up to a given duration for a token to be cancelled.
/// Returns true if the token was cancelled within the duration
/// or the underlying cancellation token source has been disposed.
/// </summary>
public static bool WaitForCancellation(this CancellationToken token, TimeSpan duration)
{
WaitHandle handle;
try
{
handle = token.WaitHandle;
}
catch
{
// eg. CancellationTokenSource is disposed
return true;
}
return handle.WaitOne(duration);
}
}