2011-02-14 40 views
53

我想安排一個任務在x毫秒開始,並能夠在開始之前(或只是在任務的開始)取消它。正確的方式來延遲任務的開始

第一次嘗試將類似

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

但我覺得應該有一個更好的辦法,因爲這將使用一個線程,而在睡眠,在此期間,也可能被取消。

我的其他選擇是什麼?

+0

它真的不開銷玉米粥和讀取真的很好(所以它是維護)。 – 2011-02-14 09:38:56

+6

@Richard發起幾百項任務並不罕見。而這段代碼不會很好地處理它。 – CodesInChaos 2011-02-14 09:48:05

+0

你可能最好使用[timer](http://msdn.microsoft.com/en-us/library/system.timers.timer.aspx)。 – Massif 2011-02-14 09:38:21

回答

8

未來的正確答案可能是Task.Delay。但是,目前只能通過Async CTP(並且在CTP中,它在TaskEx上而不是在Task上)可用。

不幸的是,因爲它只存在於CTP中,所以沒有很多好的文檔鏈接。

4

我還沒有測試過這個,但這裏是第一遍包裝方法來創建一個初始'延遲'任務或延遲後繼續。如果您發現問題,請隨時糾正。

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

Damien_The_Unbeliever mentioned,異步CTP包括Task.Delay。幸運的是,我們有反射器:

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; 
    } 
} 
3

您可以使用Token.WaitHandle.WaitOne(int32毫秒)重載方法來指定等待任務的毫秒數。但Thread.Sleep(xxx)和Token.WaitHandle.WaitOne(xxx)之間的主要區別是稍後阻塞線程,直到指定的時間過去或令牌已被取消。

下面是一個例子

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