2011-02-14 64 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(); 
}