I made a simple WPF
full screen and always on top window, which has a clock with dddd dd MMMM yyyy
HH:mm:ss
timestamp. The application is open 24/24 on a always on touch-screen, the problem is that after some time I see the clock freezed at a certain time. The application isn't freezed by itself, because I put some buttons and it is totally working.
In all versions of the clock I have this same problem!
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);
}
And in Page_Unloaded
event:
if (_clock != null && !_clock.IsCompleted)
{
_clockCancellationTokenSource?.Cancel();
_clock.Wait();
_clock.Dispose();
_clockCancellationTokenSource?.Dispose();
}
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);
}
And in Page_Unloaded
event:
if (_clock != null)
{
_clock.Stop();
_clock.Tick -= _clockTimer_Tick;
}
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);
});
}
And in Page_Unloaded
event:
_clock.Stop();
_clock.Elapsed -= _clock_Elapsed;
_clock.Dispose();
What I have tried so far:
tick
callback to see if It stops due to a UI dispatch problem or a timer problem. It's interesting that I see that the timer after a certain amount of time stops ticking.Do you have any suggestions?
The problem is you have a try/catch
that's outside the while
loop and your code is just swallowing exceptions - so when an exception is thrown it will stop the loop with no way of resuming it.
At a minimum, you need to make 2 changes to your first code example to make it work reliably:
try/catch
to inside the while
loop.Additionally, and as I remarked in the comments, your posted code was far too complicated than it needed to be:
Task.Run
nor Dispatcher.InvokeAsync
because WPF sets-up its own SynchronizationContext
which ensures that await
will resume in the UI thread by default (unless you use ConfigureAwait(false)
, of course)
ConfigureAwait(false)
in UI code!)Task.Delay
timeout of 800ms
for a clock that displays the time in seconds will result in janky and awkward updates (because at 800ms, the updates will be at 800, 1600, 2400, 3200, etc which don't align with wall-clock seconds).
All you need is this:
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 );
}