web-dev-qa-db-fra.com

Bonne façon de retarder le début d'une tâche

Je veux planifier une tâche pour qu'elle démarre en x ms et pouvoir l'annuler avant qu'elle ne démarre (ou juste au début de la tâche).

La première tentative serait quelque chose comme

var _cancelationTokenSource = new CancellationTokenSource();

var token = _cancelationTokenSource.Token;
Task.Factory.StartNew(() =>
    {
        token.ThrowIfCancellationRequested();
        Thread.Sleep(100);
        token.ThrowIfCancellationRequested();
    }).ContinueWith(t =>
    {
        token.ThrowIfCancellationRequested();
        DoWork();
        token.ThrowIfCancellationRequested();
    }, token);

Mais je pense qu'il devrait y avoir un meilleur moyen, car cela épuiserait un fil pendant le sommeil, pendant lequel il pourrait être annulé.

Quelles sont mes autres options?

53
Bruno Lopes

Comme Damien_The_Unbeliever mentionné , le CTP Async inclut Task.Delay. Heureusement, nous avons Reflector:

public static class TaskEx
{
    static readonly Task _sPreCompletedTask = GetCompletedTask();
    static readonly Task _sPreCanceledTask = GetPreCanceledTask();

    public static Task Delay(int dueTimeMs, CancellationToken cancellationToken)
    {
        if (dueTimeMs < -1)
            throw new ArgumentOutOfRangeException("dueTimeMs", "Invalid due time");
        if (cancellationToken.IsCancellationRequested)
            return _sPreCanceledTask;
        if (dueTimeMs == 0)
            return _sPreCompletedTask;

        var tcs = new TaskCompletionSource<object>();
        var ctr = new CancellationTokenRegistration();
        var timer = new Timer(delegate(object self)
        {
            ctr.Dispose();
            ((Timer)self).Dispose();
            tcs.TrySetResult(null);
        });
        if (cancellationToken.CanBeCanceled)
            ctr = cancellationToken.Register(delegate
                                                 {
                                                     timer.Dispose();
                                                     tcs.TrySetCanceled();
                                                 });

        timer.Change(dueTimeMs, -1);
        return tcs.Task;
    }

    private static Task GetPreCanceledTask()
    {
        var source = new TaskCompletionSource<object>();
        source.TrySetCanceled();
        return source.Task;
    }

    private static Task GetCompletedTask()
    {
        var source = new TaskCompletionSource<object>();
        source.TrySetResult(null);
        return source.Task;
    }
}
28
Ohad Schneider

Depuis la sortie de .NET 4.5, il existe un moyen intégré très simple de retarder une tâche: utilisez simplement Task.Delay() . dans les coulisses, il utilise l'implémentation qui ohadsc décompilé .

19
skolima

À l'avenir, la bonne réponse sera probablement Task.Delay. Cependant, cela n'est actuellement disponible que via Async CTP (et dans le CTP, c'est sur TaskEx plutôt que sur Task).

Malheureusement, comme il ne s'agit que de CTP, il n'y a pas non plus beaucoup de bons liens vers la documentation.

8
Damien_The_Unbeliever

Je n'ai pas testé cela, mais voici un premier passage aux méthodes d'encapsulation pour créer une tâche de "retard" initiale ou pour continuer après un retard. Si vous rencontrez des problèmes, n'hésitez pas à les corriger.

    public static Task StartDelayTask(int delay, CancellationToken token)
    {
        var source = new TaskCompletionSource<Object>();
        Timer timer = null;

        timer = new Timer(s =>
        {
            source.TrySetResult(null);
            timer.Dispose();
        }, null, delay, -1);
        token.Register(() => source.TrySetCanceled());

        return source.Task;
    }

    public static Task ContinueAfterDelay
      (this Task task, 
           int delay, Action<Task> continuation, 
           CancellationToken token)
    {
        var source = new TaskCompletionSource<Object>();
        Timer timer = null;

        var startTimer = new Action<Task>(t =>
        {
            timer = new Timer(s =>
            {
                source.TrySetResult(null);
                timer.Dispose();
            },null,delay,-1);
        });

        task.ContinueWith
          (startTimer, 
           token, 
           TaskContinuationOptions.OnlyOnRanToCompletion, 
           TaskScheduler.Current);
        token.Register(() => source.TrySetCanceled());
        return source.Task.ContinueWith(continuation, token);
    }
4
Dan Bryant

Regardez le TaskFactoryExtensions_Delayed dans "Programmation parallèle avec des exemples .NET 4" .

4
jyoung

Vous pouvez utiliser la méthode de surcharge Token.WaitHandle.WaitOne (int32 millisecondes) pour spécifier le nombre de millisecondes à attendre pour votre tâche. Mais différence clé entre Thread.Sleep (xxx) et Token.WaitHandle.WaitOne (xxx) qui bloque ultérieurement le thread jusqu'à ce que le temps spécifié se soit écoulé ou que le jeton ait été annulé.

Voici un exemple

void Main()
{
    var tokenSource = new CancellationTokenSource();
    var token = tokenSource.Token;

    var task = Task.Factory.StartNew(() =>
    {
        // wait for 5 seconds or user hit Enter key cancel the task
        token.WaitHandle.WaitOne(5000);
        token.ThrowIfCancellationRequested();
        Console.WriteLine("Task started its work");
    });

    Console.WriteLine("Press 'Enter' key to cancel your task");

    Console.Read();

    tokenSource.Cancel();
}
3
Vlad Bezden