在键盘事件中使用 CancellationToken 调用 Task.Delay 时出现 TaskCanceledException
Posted
技术标签:
【中文标题】在键盘事件中使用 CancellationToken 调用 Task.Delay 时出现 TaskCanceledException【英文标题】:TaskCanceledException when calling Task.Delay with a CancellationToken in an keyboard event 【发布时间】:2013-12-28 20:05:14 【问题描述】:我正在尝试延迟处理从 WinRT 中的键盘事件调用的方法(示例中为 SubmitQuery()),直到在一段时间内(本例中为 500 毫秒)没有进一步的事件。
我只希望 SubmitQuery() 在我认为用户已经完成输入时运行。
使用下面的代码,我在 Task.Delay(500,cancellationToken.Token); 时不断收到 System.Threading.Tasks.TaskCanceledException叫做。请问我在这里做错了什么?
CancellationTokenSource cancellationToken = new CancellationTokenSource();
private async void SearchBox_QueryChanged(SearchBox sender, SearchBoxQueryChangedEventArgs args)
cancellationToken.Cancel();
cancellationToken = new CancellationTokenSource();
await Task.Delay(500, cancellationToken.Token);
if (!cancellationToken.IsCancellationRequested)
await ViewModel.SubmitQuery();
【问题讨论】:
【参考方案1】:如果您在ContinueWith()
中添加一个空操作,则不会引发异常。 异常会被捕获并传递给ContinueWith()
中的tsk.Exception
属性。但它使您免于编写使您的代码变得丑陋的 try/catch。
await Task.Delay(500, cancellationToken.Token).ContinueWith(tsk => );
更新:
不用编写代码来处理异常,布尔值会更简洁。 只有在预计会延迟取消时才首选此选项!。一种方法是创建一个辅助类(虽然我不太喜欢辅助类)
namespace System.Threading.Tasks
public static class TaskDelay
public static Task<bool> Wait(TimeSpan timeout, CancellationToken token) =>
Task.Delay(timeout, token).ContinueWith(tsk => tsk.Exception == default);
public static Task<bool> Wait(int timeoutMs, CancellationToken token) =>
Task.Delay(timeoutMs, token).ContinueWith(tsk => tsk.Exception == default);
例如:
var source = new CancellationTokenSource();
if(!await TaskDelay.Wait(2000, source.Token))
// The Delay task was canceled.
(别忘了处理源)
【讨论】:
太棒了,谢谢。节省了一些非常难看的 try/catch 代码 :) 谢谢!即使它被捕获,调试器也很烦人。我认为在这些.Delay
情况下,我们期望令牌被取消,因此甚至不应该引发/捕获异常,这完美地解决了这个问题。
@Alexsandro 那是因为它被捕获并且异常在Task<TResult> tsk
参数中传递。 .ContinueWith(tsk => tsk.Exception);
这样继续的任务就可以处理异常了。在这种情况下,我们不......
我认为应该注意的是 ContinueWith 方法确实捕获并吸收了异常,但是 task.delay 之后的任何代码仍然会执行。因此,如果您在异步 Task 方法中调用它,这可能不是一个好主意,因为取消令牌的目的是取消当前任务。在这种情况下,您必须手动检查 IsCancellationRequested 的取消令牌,或者将 try/catch 留在那里..
请注意,ContinueWith
方法被 Stephen Cleary 描述为 dangerous,除非明确指定了 TaskScheduler
参数。【参考方案2】:
这是意料之中的。当您取消旧的Delay
时,会引发异常;这就是取消的工作原理。您可以在Delay
周围放置一个简单的try
/catch
来捕获预期的异常。
请注意,如果您想做这样的基于时间的逻辑,Rx 比async
更自然。
【讨论】:
能否请您提供一些资源的链接,以了解 Rx 如何更适合基于时间的逻辑 @Anupam:这是我自己的经验。【参考方案3】:奇怪的是,取消异常似乎只在取消标记位于 Task.Delay 时引发。将令牌放在 ContinueWith 上,不会抛出取消异常:
Task.Delay(500).ContinueWith(tsk =>
//code to run after the delay goes here
, cancellationToken.Token);
如果你真的想捕捉任何取消异常,你可以只链接另一个 .ContinueWith() - 它会被传递到那里。
【讨论】:
【参考方案4】:另一种抑制等待任务异常的简单方法是将任务作为单个参数传递给Task.WhenAny
:
创建一个在任何提供的任务完成后完成的任务。
await Task.WhenAny(Task.Delay(500, token)); // Ignores the exception
这种方法的一个问题是它不能清楚地传达其意图,因此建议添加评论。另一个是它导致分配(因为Task.WhenAny
的签名中的params
)。
【讨论】:
【参考方案5】:我认为值得添加评论,说明它为什么会这样工作。
关于Task.Delay
方法的TaskCancelledException,文档实际上是错误的或写得不清楚。 Delay
方法本身从不抛出该异常。它将任务转移到取消状态,而引发异常的确切原因是await
。这里使用Task.Delay
方法并不重要。它与任何其他已取消任务的工作方式相同,这就是取消预期的工作方式。这实际上解释了为什么添加延续会神秘地隐藏异常。因为是await
引起的。
【讨论】:
以上是关于在键盘事件中使用 CancellationToken 调用 Task.Delay 时出现 TaskCanceledException的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Web UI 中使用 Javascript 键盘事件