WPF 应用程序,"clock textbox" 几个小时后冻结
WPF application, "clock textbox" freezes after some hours
我做了一个简单的 WPF
全屏并且总是在顶部 window,它有一个带有 dddd dd MMMM yyyy
HH:mm:ss
时间戳的时钟。该应用程序在始终打开的触摸屏上全天候 24/24 打开,问题是 一段时间后我看到时钟在某个时间冻结。该应用程序本身并没有冻结,因为我放置了一些按钮并且它完全可以工作。
我如何更新时钟文本框
在所有版本的时钟中我都有同样的问题!
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)
!)
- 您的
Task.Delay
超时 800ms
以秒为单位显示时间的时钟将导致卡顿和尴尬的更新(因为在 800 毫秒时,更新将在 800、1600、2400、 3200 等,与挂钟秒数不一致)。
- 每当您制作连续值的离散样本(在本例中为秒数)时,您应该使用信号源的奈奎斯特频率(它是预期频率的两倍:因此要获得 1s 的样本你应该每 500 毫秒采样一次——尽管对于时间显示我更喜欢 100 毫秒的间隔来解释 UI 调度程序的抖动。如果你确实更高(例如 16 毫秒代表 60fps)你应该只更新 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 );
}
我做了一个简单的 WPF
全屏并且总是在顶部 window,它有一个带有 dddd dd MMMM yyyy
HH:mm:ss
时间戳的时钟。该应用程序在始终打开的触摸屏上全天候 24/24 打开,问题是 一段时间后我看到时钟在某个时间冻结。该应用程序本身并没有冻结,因为我放置了一些按钮并且它完全可以工作。
我如何更新时钟文本框
在所有版本的时钟中我都有同样的问题!
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 等,与挂钟秒数不一致)。- 每当您制作连续值的离散样本(在本例中为秒数)时,您应该使用信号源的奈奎斯特频率(它是预期频率的两倍:因此要获得 1s 的样本你应该每 500 毫秒采样一次——尽管对于时间显示我更喜欢 100 毫秒的间隔来解释 UI 调度程序的抖动。如果你确实更高(例如 16 毫秒代表 60fps)你应该只更新 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 );
}