我有一个名为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();
}
});
}
注意我需要取消任何以前生成的任务,并且每个衍生的任务都应该超时。
答案 0 :(得分:6)
这样做:
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();
}
答案 1 :(得分:5)
首先,如果您使用的是Visual Studio 2012+,则可以添加Microsoft.Bcl.Async包,以便为您的.NET 4.0项目添加对async
和其他高级功能的支持。
如果您使用的是Visual Studio 2010,则可以使用ParallelExtensionsExtras库附带的WithTimeout
扩展方法。该方法使用TaskCompletionSource和一个调用SetCancelled
的计时器(如果它到期)包装原始任务。
代码是here但实际方法很简单:
/// <summary>Creates a new Task that mirrors the supplied task but that
/// will be canceled after the specified timeout.</summary>
/// <typeparam name="TResult">Specifies the type of data contained in the
/// task.</typeparam>
/// <param name="task">The task.</param>
/// <param name="timeout">The timeout.</param>
/// <returns>The new Task that may time out.</returns>
public static Task<TResult> WithTimeout<TResult>(this Task<TResult> task,
TimeSpan timeout)
{
var result = new TaskCompletionSource<TResult>(task.AsyncState);
var timer = new Timer(state =>
((TaskCompletionSource<TResult>)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来响应您设置的事件或标准来创建所需的行为。
答案 2 :(得分:0)
您的代码中存在一些错误,会让您感到困惑。
首先,您使用的是Thread.Sleep而不是Task.Delay或其他一些基于计时器的方法(如果您无权访问Task.Delay,我强烈建议您编写自己的方法)。睡眠是一种阻塞等待,不能以取消令牌为条件。结果是,即使操作被取消,宝贵的线程池线程也会被扣为人质多秒。这可能会导致后来按钮按下的效果被先前的按钮按下。
其次,在等待结束时你取消_source但是这指的是当前 _值的源不是按下按钮时的值。按下之前的按钮将取消以后的按钮效果而不是自己的按钮效果。
第三,你在一个线程上处理取消令牌源,同时竞争取消另一个线程。你很幸运,你没有得到对象处置例外。
第四,在这种情况下使用异步是理想的。你提到你只是在.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
}