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();

到目前为止我尝试过的:


你有什么建议吗?

问题是你有一个 try/catch 循环之外 while 并且你的代码只是吞噬了异常 - 所以当异常被抛出时将停止循环而无法恢复它。

至少,您需要对第一个代码示例进行 2 处更改才能使其可靠地工作:

  • 你需要捕获异常并优雅地处理它们。永远不要默默地吞下异常。
  • try/catch 移动到 内部 while 循环。

此外,正如我在评论中所说,您发布的代码过于复杂,超出了需要:

  • 您不需要使用 Task.RunDispatcher.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 );
}