-1

In my WinUI 3.0 app I have a stopwatch that counts up every second. Often, but not always, it stutters while counting up. For example, the timer will stay at 0:00:04 for longer than a second, then jump to 0:00:06.

Is there something I am doing wrong?

My code:

public static DateTime startTime;
private readonly System.Timers.Timer timer = new(1000);

public MainPage()
{ 
    ViewModel = App.GetService<MainViewModel>();
    this.InitializeComponent();
    
    timer.AutoReset = true;
    timer.Elapsed += Timer_Tick;
}

private void StartButton_Click(object sender, RoutedEventArgs e)
{
    if (!timer.Enabled)
    {
        startTime = DateTime.Now;
        timer.Start();
    } 
    else
    {
        DateTime stopTime = DateTime.Now;
        timer.Stop();
        timerLabel.Text = "0:00:00";
    }
}

private void Timer_Tick(object sender, EventArgs e)
{
    DispatcherQueue.TryEnqueue(() => 
    { 
        timerLabel.Text = DateTime.Now.Subtract(startTime).ToString(@"h\:mm\:ss"); 
    });

}
Ricky Kresslein
  • 372
  • 1
  • 13
  • 1
    As the documentation makes clear timer accuracy is limited by system clock resolution from system to system. None are perfect. Use a slightly shorter interval - it doesnt matter if it goes off too soon once in a while, there wont be a change. And use a `StopWatch` instead of `DateTime`. Several expanded explanations available in existing posts – Ňɏssa Pøngjǣrdenlarp Jun 25 '22 at 18:34

1 Answers1

1

One approach (the way I solved this issue for myself) was to make my own system timer loop that fires at a greater resolution (0.1 second) and, instead of updating the Clock display based on a timer tick, it updates using a PropertyChanged event for Second which only occurs as a function of the PushDT method. I believe you could use this reliable once-per-second event to code an alternative stopwatch.

This is a shared codebase that I use for the PC, Android and iOS versions of my app so I feel confident in recommending it. Still, it's "just one way" to go about it.

static class SystemTimer
{
    static SystemTimer()
    {
        Task.Run(async() => 
        {
            while (!_dispose)
            {
                PushDT(DateTime.Now);
                await Task.Delay(100); 
            }
        });
    }

The PushDT updates bindable Minute and Second properties based on DateTime.Now.

    public static void PushDT(DateTime now)
    {
        // Using a 'now' that doesn't change within this method:
        Second = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Kind);
        Minute = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0, now.Kind);
    }
    public static event PropertyChangedEventHandler PropertyChanged;
    static DateTime _second = DateTime.MinValue;
    public static DateTime Second
    {
        get => _second;
        set
        {
            if(_second != value)
            {
                _second = value;
                PropertyChanged?.Invoke(nameof(SystemTimer), new PropertyChangedEventArgs(nameof(Second)));
            }
        }
    }
}

When (and only when) the Second property changes to a "new second" it fires a property change for Second which updates the display. The one caveat is that when the time comes to update the UI, the event needs to be marshalled onto the UI thread and how you do that depends on the platform (the SystemTimer class itself is portable).

The UI thread would have to be extraordinarily burdened to ever get a skip. I hope this suggestion helps you achieve the outcome you want.

IVSoftware
  • 5,732
  • 2
  • 12
  • 23
  • 1
    Thank you! This looks good but there seem to be some things that don't work with WinUI 3.0, such as `Invoke((MethodInvoker)delegate`. But it seems like I was able to resolve the issue by bringing the timer interval down to 100. – Ricky Kresslein Jun 26 '22 at 06:14
  • 1
    Your point is a good one and I did change the wording and remove code that was inconsistent with the `winui-3` tag. Thanks for mentioning that. The way it reads now is more what I intended to say in the first place :) – IVSoftware Jun 26 '22 at 11:40
  • 1
    And I'm glad you got `System.Timers.Timer` working! Here's my [winui-3 code](https://github.com/IVSoftware/winui-3-system-timer.git) but I didn't specifically compare performance vis-à-vis to see if this made the clock updates any more even . Having XAML LivePreview turned on seemed to load it and make it less even, too, which would make sense (but I still don't see it skipping any seconds). – IVSoftware Jun 26 '22 at 12:58