我有一个叫做Load的UI按钮。它生成一个线程,而线程又生成一个任务。任务有等待,如果任务过期,任务将被取消。Load按钮未被禁用,用户可以多次单击该按钮。每次点击之前的任务都应该被取消。
我对如何在这里使用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();
}
});
}
Note我需要取消以前生成的任务,并且每个派生的任务都应该有一个超时。
发布于 2013-12-17 16:38:56
首先,如果使用Visual 2012+,可以添加Microsoft.Bcl.Async包以向.NET 4.0项目添加对async
和其他高级功能的支持。
如果使用的是Visual 2010,则可以使用WithTimeout
库附带的ParallelExtensionsExtras扩展方法。该方法用一个TaskCompletionSource和一个计时器包装原来的任务,如果它过期了,这个定时器会调用SetCancelled
。
代码是这里,但实际方法很简单:
/// <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));
通常,您可以通过创建一个TaskCompletionSource,调用其SetResult,SetCancelled方法来响应您设置的事件或条件来创建您想要的行为。
发布于 2013-12-17 17:03:59
这样可以做到:
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();
}
发布于 2013-12-17 16:40:44
您的代码中有几个错误使事情变得很混乱。
首先,您使用的是Thread.Sleep,而不是Task.Delay或其他基于计时器的方法(如果无法访问Task.Delay,我强烈建议编写自己的方法)。睡眠是阻塞等待,不能以取消令牌为条件。结果是宝贵的线程池线程被扣为人质数秒钟,即使操作被取消。这可能会导致较早的按钮按压延迟的影响。
其次,在等待结束时,您将取消_source,但这指的是源的当前_value,而不是按下按钮时的值。较早的按钮按下将取消稍后的按钮按下效果,而不是自己的。
第三,在一个线程上配置cancel令牌源,而在另一个线程上竞相取消它。幸运的是你没有得到处理对象的异常。
第四,在这种情况下使用异步将是理想的。不过,你说过你只上过.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
}
https://stackoverflow.com/questions/20638952
复制相似问题