53 votes

Façon correcte de retarder le début d'une tâche

Je veux planifier une tâche pour commencer dans x ms et pouvoir l'annuler avant qu'elle ne commence (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 j’ai le sentiment qu’il devrait exister un meilleur moyen, car cela utiliserait un fil pendant le sommeil, pendant lequel il pourrait être annulé.

Quelles sont mes autres options?

28voto

Ohad Schneider Points 10485

Comme mentionné par Damien_The_Unbeliever , le CTP asynchrone 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;
    }
}
 

19voto

skolima Points 12221

Depuis la publication de .NET 4.5, il existe un moyen très simple et intégré de retarder une tâche: utilisez simplement Task.Delay() . en coulisse, il utilise l'implémentation décompressée par ohadsc .

8voto

Damien_The_Unbeliever Points 102139

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

Malheureusement, comme il ne s’agit que de CTP, il n’ya pas beaucoup de bons liens vers la documentation.

4voto

jyoung Points 2598

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

4voto

Dan Bryant Points 19021

Je n'ai pas testé cela, mais voici une méthode de premier passage avec les méthodes d'encapsulation pour créer une tâche initiale de "retard" ou pour continuer après un délai. 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);
    }
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X