web-dev-qa-db-fra.com

Comment "dormir" jusqu'à ce que le délai d'expiration ou l'annulation soit demandé dans .NET 4.0

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); 
}
51
Onur

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));
}
104
Frode

Alternativement, je pense que c'est assez clair:

Task.Delay(waitTimeInMs, cancellationToken).Wait(cancellationToken);

8
Fowl

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);

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.

3
MoonKnight

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 .

1
Onur

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);
    }
}
0
user2864740