问题描述
我制作了一个简单的 WPF
全屏并始终位于顶部窗口,其中有一个带有 dddd dd MMMM yyyy
HH:mm:ss
时间戳的时钟。该应用程序在始终开启的触摸屏上 24/24 全天候开放,问题是一段时间后我看到时钟在某个时间冻结。应用程序本身不会被冻结,因为我放了一些按钮,它完全可以工作。
我如何更新时钟 TextBox
在所有版本的时钟中,我都有同样的问题!
1.任务版本
private Task _clock;
private void LoadClock()
{
_clockCancellationTokenSource = new CancellationTokenSource();
_clock = Task.Run(async () =>
{
try
{
while (!_clockCancellationTokenSource.IsCancellationRequested)
{
DateTime Now = DateTime.Now;
_ = dispatcher.InvokeAsync(() =>
{
txtHour.Text = DateTime.Now.ToString("HH:mm:ss",CultureInfo.CurrentCulture);
txtDate.Text = Now.ToString("dddd dd MMMM yyyy",CultureInfo.CurrentCulture);
},dispatcherPriority.normal);
await Task.Delay(800,_clockCancellationTokenSource.Token);
}
}
catch
{
// I don't do anything with the errors here,If any
}
},_clockCancellationTokenSource.Token);
}
在 Page_Unloaded
事件中:
if (_clock != null && !_clock.IsCompleted)
{
_clockCancellationTokenSource?.Cancel();
_clock.Wait();
_clock.dispose();
_clockCancellationTokenSource?.dispose();
}
2. dispatcherTimer 版本
private dispatcherTimer _clock;
private void LoadClock(bool show)
{
_clock = new dispatcherTimer
{
Interval = TimeSpan.FromSeconds(1)
};
_clockTimer_Tick(null,null);
_clock.Tick += _clockTimer_Tick;
_clock.Start();
}
private void _clockTimer_Tick(object sender,object e)
{
var Now = DateTime.Now;
txtHour.Text = DateTime.Now.ToString("HH:mm:ss",CultureInfo.CurrentCulture);
txtDate.Text = Now.ToString("dddd dd MMMM yyyy",CultureInfo.CurrentCulture);
}
在 Page_Unloaded
事件中:
if (_clock != null)
{
_clock.Stop();
_clock.Tick -= _clockTimer_Tick;
}
3. System.Timers.Timer 版本
private Timer _clock;
private void LoadClock(bool show)
{
// Timer per gestire l'orario
_clock = new Timer(800);
_clock.Elapsed += _clock_Elapsed;
_clock.Start();
_clock_Elapsed(null,null);
}
private void _clock_Elapsed(object sender,ElapsedEventArgs e)
{
DateTime Now = DateTime.Now;
dispatcher.Invoke(() =>
{
txtHour.Text = DateTime.Now.ToString("HH:mm:ss",CultureInfo.CurrentCulture);
txtDate.Text = Now.ToString("dddd dd MMMM yyyy",CultureInfo.CurrentCulture);
});
}
在 Page_Unloaded
事件中:
_clock.Stop();
_clock.Elapsed -= _clock_Elapsed;
_clock.dispose();
到目前为止我尝试过的:
- 如您从我的代码中看到的那样更改计时器实现
- 在定时器
tick
回调中添加日志以查看它是否由于 UI 调度问题或定时器问题而停止。有趣的是,我看到计时器在一定时间后停止计时。 - 我认为这可能是 Windows 的问题,这可能会停止额外的线程,但我没有找到任何关于此的线索!
您有什么建议吗?
解决方法
问题是您有一个 try/catch
,它在 while
循环之外,而您的代码只是在吞咽异常 - 因此,当抛出异常时,它将停止循环无法恢复。
您至少需要对第一个代码示例进行 2 处更改以使其可靠运行:
- 您需要捕获异常并妥善处理它们。永远不要默默地吞下异常。
- 将
try/catch
移至while
循环的内部。
此外,正如我在评论中指出的,您发布的代码过于复杂:
- 您不需要使用
Task.Run
或Dispatcher.InvokeAsync
,因为 WPF 设置了自己的SynchronizationContext
,以确保await
将在默认情况下在 UI 线程中恢复(除非你使用ConfigureAwait(false)
,当然)- 所以永远不要在 UI 代码中使用
ConfigureAwait(false)
!)
- 所以永远不要在 UI 代码中使用
- 对于以秒为单位显示时间的时钟,您的
Task.Delay
超时800ms
将导致卡顿和尴尬的更新(因为在 800 毫秒时,更新将在 800、1600、2400、3200、等不与挂钟秒对齐)。- 每当您制作连续值的离散样本(在这种情况下:时间秒)时,您应该使用信号源的奈奎斯特频率(它是预期频率的两倍:因此要获得 1 秒的样本,您应该每 500 毫秒采集一次样本 - 虽然对于时间显示的情况,我更喜欢 100 毫秒的间隔来考虑 UI 调度程序的抖动。如果你做得更高(例如 60 fps 为 16 毫秒),你应该只在秒值发生变化时更新 UI,否则你就是在浪费 CPU 和 GPU 周期)。
你只需要这个:
private bool clockEnabled = false;
private readonly Task clockTask;
public MyPage()
{
this.clockTask = this.RunClockAsync( default );
}
private override void OnLoad( ... )
{
this.clockEnabled = true;
}
private async Task RunClockAsync( CancellationToken cancellationToken = default )
{
while( !cancellationToken.IsCancellationRequested )
{
try
{
if( this.clockEnabled )
{
// WPF will always run this code in the UI thread:
this.UpdateClockDisplay();
}
await Task.Delay( 100 ); // No need to pass `cancellationToken` to `Task.Delay` as we check `IsCancellationRequested ` ourselves.
}
catch( OperationCanceledException )
{
// This catch block only needs to exist if the cancellation token is passed-around inside the try block.
return;
}
catch( Exception ex )
{
this.log.LogError( ex );
MessageBox.Show( "Error: " + ex.ToString(),etc... );
}
}
}
private void UpdateClockDisplay()
{
DateTime now = DateTime.Now;
// Don't cache CurrentCulture,because the user can change their system preferences at any time.
CultureInfo ci = CultureInfo.CurrentCulture;
this.txtHour.Text = now.ToString( "HH:mm:ss",ci );
this.txtDate.Text = now.ToString( "dddd dd MMMM yyyy",ci );
}