0

我做了一个简单的WPF全屏,总是在顶部窗口,它有一个带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 的问题,可能会停止额外的线程,但我没有找到任何线索!

你有什么建议吗?

4

1 回答 1

1

问题是你有一个在循环之外try/catch的东西,你的代码只是在吞噬异常——所以当一个异常被抛出时,它会停止循环而无法恢复它。while

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

  • 您需要捕捉异常并优雅地处理它们。永远不要默默地吞下异常。
  • try/catchto 移到循环内while

此外,正如我在评论中所说,您发布的代码比它需要的复杂得多:

  • 您不需要使用Task.Run,也不需要使用,Dispatcher.InvokeAsync因为 WPF 设置了它自己的SynchronizationContext,以确保await默认情况下将在 UI 线程中恢复(当然,除非您使用ConfigureAwait(false)
    • 所以永远不要ConfigureAwait(false)在 UI 代码中使用!)
  • 您对以秒为单位显示时间的时钟的Task.Delay超时800ms将导致更新不稳定和尴尬(因为在 800 毫秒时,更新将在 800、1600、2400、3200 等与挂钟秒数不一致)。
    • 每当您制作连续值的离散样本时(在这种情况下:以秒为单位),那么您应该使用信号源的奈奎斯特频率(这是预期频率的两倍:所以要获得 1s 的样本,您应该每隔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 );
}
于 2021-03-26T09:31:59.687 回答