Modo corretto per ritardare l’inizio di un’attività

Voglio pianificare un’attività da avviare in x ms e riuscire a cancellarla prima che inizi (o solo all’inizio dell’attività).

Il primo tentativo sarebbe qualcosa di simile

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

Ma sento che dovrebbe esserci un modo migliore, dato che questo userebbe un thread durante il sonno, durante il quale potrebbe essere cancellato.

Quali sono le mie altre opzioni?

Come Damien_The_Unbeliever menzionato , il CTP Async include Task.Delay . Fortunatamente, abbiamo 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(); 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(); source.TrySetCanceled(); return source.Task; } private static Task GetCompletedTask() { var source = new TaskCompletionSource(); source.TrySetResult(null); return source.Task; } } 

Dal momento che .NET 4.5 è stato rilasciato, esiste un modo molto semplice per ritardare un’attività: basta usare Task.Delay() . dietro le quinte, usa l’implementazione che è stata decompilata .

La risposta corretta in futuro sarà probabilmente Task.Delay . Tuttavia, questo è attualmente disponibile solo tramite Async CTP (e nel CTP, è su TaskEx anziché su Task).

Sfortunatamente, poiché è solo in CTP, non ci sono molti buoni collegamenti alla documentazione.

Guarda il TaskFactoryExtensions_Delayed in “Parallel Programming with .NET 4 Samples” .

Non ho provato questo, ma qui è un primo passaggio ai metodi wrapper per creare un’attività iniziale “Delay” o per continuare dopo un ritardo. Se trovi problemi, sentiti libero di correggere.

  public static Task StartDelayTask(int delay, CancellationToken token) { var source = new TaskCompletionSource(); 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 continuation, CancellationToken token) { var source = new TaskCompletionSource(); Timer timer = null; var startTimer = new Action(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); } 

È ansible utilizzare il metodo di overload Token.WaitHandle.WaitOne (int32 millisecondi) per specificare il numero di millisecondi che attendono l’attività. Ma la differenza fondamentale tra Thread.Sleep (xxx) e Token.WaitHandle.WaitOne (xxx) che blocca in seguito thread fino al tempo specificato trascorso o il token è stato annullato.

Ecco un esempio

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