0

My DispatcherTimer is lagging when i hover over the Application Window, I tested the same thing with Windows Forms and the same Code, only with the Timer from Forms, it worked without any lag.

Here is my Code:

{
    double dps1 = 2;
    double count;

    DispatcherTimer DPS = new DispatcherTimer();
    public MainWindow()
    {
        InitializeComponent();

        DPS.Tick += new EventHandler(DPS_tick);
        DPS.Interval = new TimeSpan(1);
    }

    private void DPS_tick(object sender, EventArgs e)
    {
        count += dps1 / 2500;
        lbl.Content = count;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        DPS.Start();
    }

And Here is a GIF of the actual Program: https://i.imgur.com/d91bxJD.gifv

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
Asura Nico
  • 13
  • 3
  • It might be me, but I don't see any lag in the video. When exactly should I be looking? – Camilo Terevinto Mar 13 '18 at 16:59
  • Because it's not windows forms, and you're interacting with the UI thread while your mouse is hovering over the form. Since the DispatcherTimer (in this case) uses the UI thread, it needs to have its work scheduled on the UI thread, which you're messing about with. Try a regular Timer and use Dispatcher.Invoke to update the UI. –  Mar 13 '18 at 17:00
  • Also be aware that `DPS.Interval = new TimeSpan(1)` sets the Interval to 100 nanoseconds. – Clemens Mar 13 '18 at 17:01
  • @Will: I could ahve sworn it was the other way around - old Code like Windows.Forms.Timer runs on the UI Thread. Dispatcher does not. – Christopher Mar 13 '18 at 17:02
  • Ok, thanks for your help guys :D – Asura Nico Mar 13 '18 at 17:07
  • 2
    DispatcherTimer operates much like timers do in the native winapi, their notifications have the lowest priority. User input always goes ahead of it, lots of MouseMove notifications in this case. WPF uses DispatcherPriority to control this, the default constructor for DispatcherTimer uses DispatcherPriority.Background. But it is nice about it, [you can mess with that](https://msdn.microsoft.com/en-us/library/ms615008(v=vs.110).aspx). Do note that you need to test this first in the Release build without a debugger attached, I'm a bit suspicious of the visual tree debugging feature. – Hans Passant Mar 13 '18 at 17:09
  • Timers can be confusing. You have to consider where the counting is done (and thus if they have Metronome Quality), where the Tick event is Raised (UI or Worker Thread; and thus if invoking is nessesary). And if ticks can not be processed fast enough (Ui Write overhead), if it jsut piles more onto the queue or waits for one to be processed. https://web.archive.org/web/20150329101415/https://msdn.microsoft.com/en-us/magazine/cc164015.aspx – Christopher Mar 13 '18 at 17:13
  • @Christopher IIRC the DispatcherTimer snags Dispatcher.Current on creation... that's the UI thread, since he creates it in the UI thread. And it's tick is definitely being affected by the UI thread being distracted by that pointer. I've got a repro that fixes by using a different dispatcher. –  Mar 13 '18 at 17:29
  • Er, different timer, dangit. –  Mar 13 '18 at 17:41

1 Answers1

1

First off, you're using the UI dispatcher to schedule your timer tick. To prove this, add the following to your constructor.

if(DPS.Dispatcher != Dispatcher.CurrentDispatcher || // UI thread, UI dispatcher
   DPS.Dispatcher != Application.Current.Dispatcher || // Also the UI dispatcher
   DPS.Dispatcher != Dispatcher) // the window's dispatcher
    throw new InvalidOperationException("Will is wrong");

You'll note I'm right. So, the UI thread is busy with mouse events when you wiggle your mouse around, which as Hans said have much higher priority than your timer tick. Feh on it.

You can get around this by using a different timer implementation that runs on a background thread, then trapse over to the UI thread to update the UI on tick.

I'm a fan of MVVM, and bindings automatically handle invoking updates on the UI thread for you, so my repro is slightly different than yours.

First, add a binding to the label in the UI.

<Label Content="{Binding Count}" />

We'll reuse the Click event on the button, creating an ICommand for that is a little overboard. Next, update your Window's code to look like this

public partial class MainWindow : Window
{
    VM vm = new VM();
    public MainWindow()
    {
        InitializeComponent();
        DataContext = vm;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        vm.Start();
    }

    class VM : INotifyPropertyChanged
    {
        double dps1 = 2;
        double count;
        Timer timer;
        PropertyChangedEventArgs args = new PropertyChangedEventArgs(nameof(Count));
        public VM()
        {
            timer = new Timer();
            timer.Interval = .0001;
            timer.Elapsed += (o, e) => Count = (count += dps1 / 2500);
        }
        public double Count
        {
            get
            {
                return count;
            }
            set
            {
                count = value;
                PropertyChanged?.Invoke(this, args);
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        public void Start()
        {
            timer.Start();
        }
    }
}

I'm using System.Windows.Threading.Timer to handle my ticks, which has an Interval in ms (.0001ms is a tick, roughly sorta). If you run this example, your UI won't lag anymore.