1

I have encountered a problem when I tried to control frame rate in WPF by using System.Windows.Threading.DispatcherTimer instance.

In order to try effectivity of DispatcherTimer, I created a simple WPF Demo with a single window which has a textbox and a button. When the button is clicked, a DispatcherTimer instance begins to tick according to the double number in the textbox as the interval and a StopWatch starts at the same time, a counter variable increases by 1 on every DispatcherTimer tick. When StopWatch.ElaspedMilliSeconds > 1000 (more than 1 second passed), the timer stops ticking and the stopwatch resets as well, a message box pops up to show value of the counter.

So the counter value is supposed to be around 30 if I input 33.3(1000/30). But the result turns out to be around 20. I ask for help whether there is anyone can help check what seems to be the problem in my source code below. Thanks in advance.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace TimerDemo
{
   public partial class MainWindow : Window
   {
      private System.Windows.Threading.DispatcherTimer _timer = new System.Windows.Threading.DispatcherTimer();
      public MainWindow()
      {
         InitializeComponent();
         double ticks = 0L;
         System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();

         int frameCount = 0;

         //There is a textbox "txtTicks" which accepts a millisecond value
         //And a button "btn", by clicking the button the dispatchertimer & 
         //stopwatcher are started.
         _timer.Tick += (sender, e) =>
         {
            frameCount++;
            System.Diagnostics.Debug.WriteLine(watch.ElapsedMilliseconds);
            if (watch.ElapsedMilliseconds > 1000)
            {
               _timer.Stop();
               watch.Reset();
               MessageBox.Show(string.Format("Already 1 second! FrameCount: {0}", frameCount));
               frameCount = 0;
            }
         };

         this.btn.Click += (sender, e) =>
         {
            double.TryParse(this.txtTicks.Text, out ticks);
            if (ticks != 0.0)
            {
               _timer.Interval = TimeSpan.FromMilliseconds(ticks);
            }
            _timer.Start();
            watch.Start();
         };
      }
   }
}

Running result as below(Rookie of Stackoverflow, cannot upload image yet):

https://i.stack.imgur.com/u6W2s.png

fatbigbright
  • 13
  • 1
  • 4
  • 1
    You can't *control* the frame rate of a WPF application by a DispatcherTimer. From [MSDN](https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatchertimer(v=vs.110).aspx): *Timers are not guaranteed to execute exactly when the time interval occurs, but they are guaranteed to not execute before the time interval occurs.* Please explain what exactly you are trying to achieve. – Clemens Jul 06 '15 at 07:10
  • Thanks for comment, Clemens. What I want to achieve is to use DispatcherTimer to control frame rate in WPF canvas Animation. What i feel strange is the real frame rate is around 20 instead of 30, but it is supposed to be 30. I understand it is not guaranteed to execute exactly. But it still seems that the real frame rate has something to do with what it is supposed to be. And I wonder if timers is not a good way to control canvas animation, what is the better one? Thanks in advance. – fatbigbright Jul 06 '15 at 07:21
  • 1
    You may want to take a look at the *Per-Frame Animation: Bypass the Animation and Timing System* section in the [Property Animation Techniques Overview](https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatchertimer(v=vs.110).aspx) article on MSDN. – Clemens Jul 06 '15 at 07:36
  • Without [a good, _minimal_, _complete_ code example](https://stackoverflow.com/help/mcve) that reliably reproduces the problem, it's impossible to say for sure what's best in your case. But commenter Clemens has offered useful advice; 20 fps is about as fast as any timer/thread based scheduling can get, because thread scheduling happens on a granularity of 50ms. Why do you need a specific frame rate? Why not just set a WPF animation and let the framework deal with presenting the animation as best it can? – Peter Duniho Jul 06 '15 at 20:38
  • @PeterDuniho Thanks for comment. Actually what I am writing is a minigame such as Tetris by using WPF canvas. I am trying to find a way to gain a steady frame rate such as 30fps. I think Clemens's comment about Render on a Per Frame Interval is helpful for my requirement. But I still have one more question: what you explained about 20fps is that thread scheduling happens on a granularity of 50ms, is that means it cannot be more frequent than 20fps? I have tried the demo which I have pasted above, I found if I input 3, for example, I can get a much larger value. Still wonder the reason. – fatbigbright Jul 07 '15 at 14:22
  • _"is that means it cannot be more frequent than 20fps"_ -- no, you can definitely have frame rates higher than that. But you do so by having a single thread rendering as fast as it can, or at least not relying on Windows' internal timing mechanisms. The 50ms issue doesn't restrict frame rate per se; it restricts how _accurately_ mechanisms can operate that depend on Windows' timing and thread scheduling logic. – Peter Duniho Jul 07 '15 at 15:42
  • @PeterDuniho Thanks. I have also looked up . In "Animation Performance" section of Chapter 15, it says "WPF attempts to keep animations running at 60 frames per second". If Storyboard is used, frame rate can be set by assigning value to Timeline.DesiredFrameRate. For my case, what I am creating is frame-base animation, so I think what I can do is to simply insert my code into CompositionTarget.Rendering event for every frame without paying attention to framerate. Anyway, thanks again for both of your comments. – fatbigbright Jul 09 '15 at 07:54

1 Answers1

2

Since WPF (and most other rendering engines) does not take an equal time amount to render every frame, and since different computations can further delay the interval between frames, you are right in using CompositionTarget.Rendering to "clock" the rendering intervals, but you can still use a "wall clock" timer to update your game at regular, rigorously controlled intervals.

In the excellent book "Game Programming Patterns" (freely available online) you can find the very useful Game Loop Pattern.

In its full form, this is what you would put inside a handler for the CompositionTarget.Rendering event (in pseudo-code):

  double current = getCurrentTime();
  double elapsed = current - previous;
  previous = current;
  lag += elapsed;

  processInput();

  while (lag >= MS_PER_UPDATE)
  {
    update();
    lag -= MS_PER_UPDATE;
  }

  render();

In C#, you could implement getCurrentTime() either by calling DateTime.Now or by using Stopwatch.Elapsed, for example.

heltonbiker
  • 26,657
  • 28
  • 137
  • 252