2011-02-14 18 views
53

Quiero programar una tarea para que se inicie en x ms y poder cancelarla antes de que comience (o solo al comienzo de la tarea).Forma correcta de retrasar el inicio de una Tarea

El primer intento sería algo así como

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

pero siento que debe haber una mejor manera, ya que esto sería utilizar un hilo, mientras que en el sueño, durante el cual podría ser cancelada.

¿Cuáles son mis otras opciones?

+0

Su realmente no Mush gastos generales y se lee muy bien (por lo que es mantenible). –

+6

@Richard No es raro despedir unos cientos de tareas. Y este código no lo tratará bien. – CodesInChaos

+0

Probablemente sea mejor con un [temporizador] (http://msdn.microsoft.com/en-us/library/system.timers.timer.aspx). – Massif

Respuesta

8

La respuesta correcta en el futuro probablemente será Task.Delay. Sin embargo, actualmente solo está disponible a través del Async CTP (y en el CTP, está en TaskEx en lugar de Task).

Desafortunadamente, debido a que solo está en CTP, tampoco hay muchos enlaces buenos para la documentación.

+4

El '.Delay()' y otros métodos basados ​​en TAP ahora están disponibles para .NET 4.0 fuera del CTP Async a través del [Async Targeting Pack] (http://www.microsoft.com/en-us/download/ details.aspx? id = 29576). Ignore las afirmaciones de que solo funciona en VS11, funciona maravillosamente en VS2010 ya que es solo una biblioteca. –

4

No he probado esto, pero aquí hay una primera pasada en los métodos de envoltura para crear una Tarea de "Delay" inicial o para continuar después de un Delay. Si encuentra problemas, no dude en corregirlos.

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

Puede implementar 'ContinueAfterDelay' usando' StartDelayTask' en una línea: 'task.ContinueWith (t => StartDelayTask (delay, token)). Unwrap().ContinueWith (continuación, token) 'también veo mi respuesta para la implementación de Microsoft (un poco más completa) –

28

Como Damien_The_Unbeliever mentioned, el CTP asíncrono incluye Task.Delay. Afortunadamente, tenemos 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; 
    } 
} 
+1

No entiendo algo acerca de este código: ¿El delegado del temporizador no captura el ctr que se actualiza, en lugar del que pertenece? a la ctr que realmente representa un delegado ?? – Elliot

+1

En cuanto a mi comentario obove: Parece que solo necesito ir y estudiar adecuadamente los cierres. – Elliot

+0

@Elliot ctr es una variable capturada. La variable real se captura, por lo que los cambios realizados en ella después de la captura aún se mantienen. Jon skeet tiene un artículo sobre ellos: http://csharpindepth.com/Articles/Chapter5/Closures.aspx –

19

Desde .NET 4.5 ahora ha sido liberado, hay una muy simple forma integrada de retrasar una tarea: sólo tiene que utilizar Task.Delay(). detrás de escena, usa la implementación que ohadsc decompiled.

3

Puede utilizar el método de sobrecarga Token.WaitHandle.WaitOne (int32 milisegundos) para especificar el número de milisegundos que esperará su tarea. Pero la diferencia clave entre Thread.Sleep (xxx) y Token.WaitHandle.WaitOne (xxx) que luego bloquea el hilo hasta que transcurre el tiempo especificado o el token se ha cancelado.

Aquí es un ejemplo

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

Según MSDN WaitHandle.WaitOne también bloquea el hilo. "Bloquea el hilo actual hasta que el WaitHandle actual reciba una señal, usando un entero con signo de 32 bits para especificar el intervalo de tiempo en milisegundos". https://msdn.microsoft.com/en-us/library/cc189907(v=vs.110).aspx – user3285954

Cuestiones relacionadas