我想在引发事件时经过一些延迟之后调用一个方法,但是任何后续事件都应该“重新启动”这个延迟。快速举例说明,视图应该在滚动条位置改变时更新,但仅在用户完成滚动后1秒。
现在我可以看到很多实现的方法,但最直观的是使用Task.Delay
+ ContinueWith
+取消令牌。然而,我遇到了一些问题,更准确地说,后续调用我的函数导致TaskCanceledException
异常,我开始想知道我如何才能摆脱它。以下是我的代码:
private CancellationTokenSource? _cts;
private async void Update()
{
_cts?.Cancel();
_cts = new();
await Task.Delay(TimeSpan.FromSeconds(1), _cts.Token)
.ContinueWith(o => Debug.WriteLine("Update now!"),
TaskContinuationOptions.OnlyOnRanToCompletion);
}
我已经找到了一个工作相当不错的变通办法,但我想使第一个想法的工作。
private CancellationTokenSource? _cts;
private CancellationTokenRegistration? _cancellationTokenRegistration;
private void Update()
{
_cancellationTokenRegistration?.Unregister();
_cts = new();
_cancellationTokenRegistration = _cts.Token.Register(() => Debug.WriteLine("Update now!"));
_cts.CancelAfter(1000);
}
5条答案
按热度按时间aurhwmvo1#
根据我自己的经验,我处理过很多类似你描述的场景,例如,* 在鼠标停止移动一秒后更新某个东西 * 等等。
很长一段时间,我会用你描述的方法来重启计时器,即取消一个旧任务,然后启动一个新任务。但我从来不喜欢这种混乱的方式,所以我想出了一个替代方案,在生产代码中使用。长期来看,它被证明是相当可靠的。它利用了捕获到的与任务相关的上下文。
TaskCanceledException
的多个示例不再出现。另一个不错的好处是,它不依赖于平台计时器,在iOS/Android中和在WinForms/WPF中一样好用。
为了便于演示,可以在一个快速控制台演示中执行此操作,其中
MockUpdateView()
操作以500 ms的间隔发送到WDT 10次,但它只执行一次,即在收到最后一次重新启动后500 ms。因此,通过500 ms乘以10次重启,这验证了从启动开始5秒时的一个事件。
ruyhziif2#
您应该考虑使用微软的被动框架(又名Rx)-NuGet
System.Reactive
并添加using System.Reactive.Linq;
。您没有说明您正在使用的UI,因此对于Windows窗体也添加
System.Reactive.Windows.Forms
,对于WPF添加System.Reactive.Windows.Threading
。然后您可以执行以下操作:
query
为每个Scroll
事件触发,并启动一个1秒计时器。Switch
操作符监视每个Timer
生成,并仅连接到最新生成的事件,从而忽略先前的Scroll
事件。就是这样。
滚动暂停1秒后,单词"Hello"将写入控制台。如果您再次开始滚动,则在每隔1秒暂停后,它将再次触发。
bnlyeluc3#
你可以合并一个状态变量和一个延迟来避免干扰定时器或者任务取消。这要简单得多IMO。
将此状态变量添加到类/窗体中:
以下是您的刷新方式:
如果您重复调用
RefreshInOneSecond
,它会将_nextRefresh
时间戳推到以后,因此任何正在进行的刷新都不会起任何作用。Demo on DotNetFiddle
798qvoo84#
一种方法是创建一个计时器,并在用户执行操作时重置它。
有多个计时器可供选择,我相信其他计时器也可能有相同的模式。如果您使用WPF,DispatchTimer可能是最合适的。
请注意,
System.Timers.Timer
和Task.Delay
都在后台使用System.Threading.Timer
。可以直接使用它,只需调用.Change方法来重置它。但请注意,这会在任务池线程上引发事件,因此您需要提供自己的同步。6za6bjd05#
我在一个JavaScript应用程序中使用Timer实现了相同的场景。我相信在.NET世界中也是一样的。无论如何,当用户使用
Task.Delay()
重复调用一个方法时,处理这种用例会给GC和线程池带来更大的压力