WPF 应用程序,“时钟文本框”在几个小时后冻结

Posted

技术标签:

【中文标题】WPF 应用程序,“时钟文本框”在几个小时后冻结【英文标题】:WPF application, "clock textbox" freezes after some hours 【发布时间】:2021-06-23 02:43:41 【问题描述】:

我制作了一个简单的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 的问题,可能会停止额外的线程,但我没有找到任何线索!

你有什么建议吗?

【问题讨论】:

您的问题是您缺少异常。永远不要吞下异常。添加一些适当的错误处理代码。 另外,您不需要使用Task.RunInvoke,因为 WPF 会为您添加自己的同步上下文。 延迟 800 毫秒会给你一个 janky UI。您应该使用 500 毫秒,或 1 秒的某个整数部分。 500ms 是您可以使用的最高延迟(和最低频率)。你需要阅读Nyquist frequency 【参考方案1】:

问题是您有一个try/catch,它在while 循环之外,而您的代码只是在吞噬异常-因此,当抛出异常时,它将停止循环而无法恢复它。

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

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

此外,正如我在 cmets 中所说,您发布的代码过于复杂:

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

【讨论】:

我去试试看告诉你,谢谢! 我不敢相信我没有注意到我将 try..catch 放在循环之外。也非常感谢其他建议!

以上是关于WPF 应用程序,“时钟文本框”在几个小时后冻结的主要内容,如果未能解决你的问题,请参考以下文章

在打开几个小时后反应应用程序在 chrome 中冻结

Python 脚本在几个小时后停止

Azure Web 应用服务在几个小时内变得不可用

为啥谷歌地图在几次回帖后冻结页面

MongoDB - 在几个小时的时间范围内查询

Spring Boot:在使用 Hibernate、JDBC 和 MySQL 几个小时不活动后通信链接失败 [重复]