CancellationToken和CancellationTokenSource-如何使用它?

我有一个名为Load的UI按钮。 它产生一个线程,反过来产生一个任务。 任务等待,如果过期,任务将被取消。 “加载”按钮未禁用,用户可以多次单击它。 每次点击它都应该取消上一个任务。

我对如何在这里使用CancellationTokenSource和CancellationToken感到困惑。 以下是代码。 您能否建议如何使用它以及以下用法是否有任何问题? 请不要异步,因为我们还没有。

CancellationTokenSource _source = new CancellationTokenSource(); public void OnLoad() { //Does this cancel the previously spawned task? _source.Cancel(); _source.Dispose(); _source = new CancellationTokenSource(); var activeToken = _source.Token; //Do I need to do the above all the time or is there an efficient way? Task.Factory.StartNew(() => { var child = Task.Factory.StartNew(() => { Thread.Sleep(TimeSpan.FromSeconds(20)); activeToken.ThrowIfCancellationRequested(); }, activeToken); if (!child.Wait(TimeSpan.FromSeconds(5))) { _source.Cancel(); } }); } 

注意我需要取消任何以前生成的任务,并且每个生成的任务都应该超时。

首先,如果您使用的是Visual Studio 2012+,则可以添加Microsoft.Bcl.Async包,以便为您的.NET 4.0项目添加对async和其他高级function的支持。

如果您使用的是Visual Studio 2010,则可以使用ParallelExtensionsExtras库附带的WithTimeout扩展方法。 该方法使用TaskCompletionSource和一个调用SetCancelled的计时器包装原始Task(如果它到期)。

代码在这里,但实际的方法很简单:

  /// Creates a new Task that mirrors the supplied task but that /// will be canceled after the specified timeout. /// Specifies the type of data contained in the /// task. /// The task. /// The timeout. /// The new Task that may time out. public static Task WithTimeout(this Task task, TimeSpan timeout) { var result = new TaskCompletionSource(task.AsyncState); var timer = new Timer(state => ((TaskCompletionSource)state).TrySetCanceled(), result, timeout, TimeSpan.FromMilliseconds(-1)); task.ContinueWith(t => { timer.Dispose(); result.TrySetFromTask(t); }, TaskContinuationOptions.ExecuteSynchronously); return result.Task; } 

您可以在创建任务后立即使用它:

 var myTask=Task.Factory.StartNew(()=>{...}) .WithTimeout(TimeSpan.FromSeconds(20)); 

通常,您可以通过创建调用其SetResult,SetCancelled方法的TaskCompletionSource来响应您设置的事件或条件来创建所需的行为。

这样做:

  private CancellationTokenSource _cancelTasks; // this starts your process private void DoStuff() { _cancelTasks = new CancellationTokenSource(); var task = new Task(() => { /* your actions here */ }, _cancelTasks.Token); task.Start(); if (!task.Wait(5000)) _cancelTasks.Cancel(); } 

您的代码中存在一些错误,使事情变得混乱。

首先,您使用的是Thread.Sleep而不是Task.Delay或其他一些基于计时器的方法(如果您无权访问Task.Delay,我强烈建议您自己编写)。 睡眠是一种阻塞等待,不能以取消令牌为条件。 结果是,即使操作被取消,宝贵的线程池线程也会被扣为人质多秒。 这可能导致后面的按钮按下的效果被先前的按钮按下。

其次,在等待结束时你取消了_source但是这指的是源的当前 _值而不是按下按钮时的值。 按下之前的按钮将取消以后的按钮按下效果而不是自己的效果。

第三,你在一个线程上处理取消令牌源,同时竞争在另一个线程上取消它。 你很幸运,你没有得到对象处置exception。

第四,在这种情况下使用异步是理想的。 你提到你只是在.Net 4.0上。

修复前三件事应该会让事情变得更容易理解:

 CancellationTokenSource _prevSource = new CancellationTokenSource(); public void OnButtonPress() { var curSource = new CancellationTokenSource(); _prevSource.Cancel(); _prevSource = curSource; MyCustomDelay(TimeSpan.FromSeconds(5), curSource.Token).ContinueWith(t => { curSource.Cancel(); }, TaskContinuationOptions.OnlyOnRanToCompletion); var r = MyCustomDelay(TimeSpan.FromSeconds(20), curSource.Token).ContinueWith(t => { curSource.ThrowIfCancellationRequested(); }, TaskContinuationOptions.OnlyOnRanToCompletion); // after 5 seconds the token r's delay is conditions on is cancelled // so r is cancelled, due to the continuation specifying OnlyOnRanToCompletion // the ThrowIfCancellationRequested line won't be executed // although if we removed the cancel-after-5-seconds bit then it would be }