C# Timers, having issues

Started by
14 comments, last by keinmann 13 years, 6 months ago
I need to use a timer to call the Draw method of my graphics engine which draws the next frame to the screen buffer and calls Invalidate() to refresh the form. I'd like to have a steady framerate, and 60FPS would be ideal. The problem is I've tried every type of timer (that I know of) and none of them are reliable enough.

If I use the following code (paraphrased a bit), which draws a large triangle on the screen. It rotates the triangle every frame and displays the frame rate as well:

Thread drawLoop = new Thread(new ThreadStart(Draw));drawLoop.Start()...void Draw(){    for(;;)    {        graphics.Clear();        graphics.Draw();        this.Invalidate();        Thread.Sleep(16); // has to be integer value, 16ms = 62.5fps    }}


It show's a very solid 66.7FPS on the screen and the animation is very smooth.

However, when I tried to implement this as a timer and EventHandler, e.g. :

var timer = new System.Timers.Timer(1000.0f / 60.0f); //use 60FPS (also tried 16ms as before)timer.Elapsed += new ElapsedEventHandler(Draw);timer.Start()void Draw(object source, ElapsedEventArgs e){    graphics.Clear();    graphics.Draw();    this.Invalidate();}


Besides System.Timers.Timer, I've also used System.Windows.Forms.Timer and System.Threading.Timer and they all yield identical results; slow and jerky animation with frame rates that jump around +/- 50% and are otherwise lower than they should be (e.g. 16ms should be ~62FPS but you get closer to 40FPS).

This is extremely frustrating and I hope someone here has an answer as to what I should do.

Advertisement
Most games use a loop like the first one to ensure that they have more control over the framerate. Why do you need to use a timer? The Windows events can be pumped, or you can use the Application.Idle trick to run game-related processing as fast as possible.
Should I maybe use a BackgroundWorker?
NEVER use Thread.Sleep(int) to try to keep something at a constant beat or synchronize threads. Not only is it not very accurate, but if you use it for thread synchronization, you're shooting yourself in the foot. It's ok if you want to add something like Thread.Sleep(1) to just let it rest for a little sliver of time, but never ever ever try to use this as a time keeping or synchronization mechanism.

You need to implement a "GameClock" like you can see in the SlimDX Samples (I also don't think you mentioned what API you're using). Basically, you're going to use System.Diagnostics.Stopwatch to do your work. You use Stopwatch.GetTimeStamp() to get the current amount of system ticks (keep in mind, you aren't instantiating a Stopwatch, but using the static methods/properties). When your GameClock object updates again, you get the current timestamp and find the amount of ticks which elapsed since the last update. You can convert that to milliseconds by getting the Stopwatch.Frequency property, which tells you how many ticks are in a second on the local system.

Using such a mechanism, your game loop will use an accumulator-like mechanism to keep update and render calls properly synced.

EDIT: If you like, I could post my implementation for you to learn from. Just don't take my code verbatim and stick it in your project. Read it, and write one that suits your needs properly. Otherwise, you're cheating yourself out of the experience and fun! :)
I have heard of similar "problems" using the .net timer class. The explanation was that when you keep no reference to the timer object, the garbage collector might collect the timer before the specified time has passed and your function has been called. Maybe that can explain your strange results?
@keinmann

I'm not using any APIs (other than GDI+) since this is just a simple software rendering exercise I'm doing. I'd like to see your code, or maybe a link to something similar.

I have a problem when I use a sleep() that's lower than 2ms, I draw the frame rate to the top left corner and it doesn't display if it's running too fast.
bump
Here's a timer class I used for a C# game server. Create an instance of a TimeClock and then Update() it in your main program update loop.

using System;using System.ComponentModel;using System.Diagnostics;using System.Threading;using System.Windows.Forms;public class TimeClock{    private Stopwatch stopWatch;    // time elapsed since the previous update    public float elapsedMS = 0f;    private float currentTime = 0f;    private float oldTime = 0f;    public TimeClock()    {        stopWatch = new Stopwatch();        stopWatch.Reset();        stopWatch.Start();    }    // calculate elapsed ms since update() was last called    public void Update()    {        oldTime = currentTime;        currentTime = (float)stopWatch.ElapsedMilliseconds;        elapsedMS = currentTime - oldTime;    }}


In your code somewhere you'd do something like this:

// timeclock and a time variable float elapsedMilliseconds = 0;TimeClock timeClock = new TimeClock();// do this somewhere in your update routineYourApp.Update(){      // update the elapsed time   timeClock.Update();   elapsedMilliseconds += timeClock.elapsedMS;   // check if 16 ms have passed   // if so, reset the timer and fire the event   if(elapsedMilliseconds >= 16f)   {       elapsedMillisecond %= 16f;       DoSomething();   }}
The best way is NOT to instantiate the Stopwatch class and use Stop/Start/Reset. You should use the GetTimeStamp() method to get system ticks and the Frequency property to calculate real elapsed time. That's the most accurate way.

Check your pm box, and I'm going to give you a copy of the original file, documentation included. Here I'm going to post it with no documentation for readability's sake, since you can't collapse things in the forums' text boxes.

#region Using Statements/* System References :: */using System;using System.Diagnostics;/* SlimDX References :: *//* Engine References :: */#endregioninternal sealed class GameClock{    /*-----------------------------------------------------------------------------     * CONSTRUCTORS & INITIALIZATION ::     *-----------------------------------------------------------------------------*/    internal GameClock()    {        Reset();    }    /*-----------------------------------------------------------------------------     * FIELDS & PROPERTIES ::     *-----------------------------------------------------------------------------*/    long count;    const long nsfactor = 10000000L;    public bool IsRunning { get { return isrunning; } }    bool isrunning = false;    internal long Frequency { get { return Stopwatch.Frequency; } }    internal bool isHighResolution { get { return Stopwatch.IsHighResolution; } }    internal static long Timestamp { get { return Stopwatch.GetTimestamp(); } }    internal TimeSpan Elapsed { get { return elapsed; } }    TimeSpan elapsed;    internal TimeSpan Total { get { return total; } }    TimeSpan total;    /*-----------------------------------------------------------------------------     * METHODS & IMPLEMENTATIONS ::     *-----------------------------------------------------------------------------*/    internal void Start()    {        isrunning = true;        count = Timestamp;    }    internal void Stop()    {        isrunning = false;    }    internal void Reset()    {        count = Timestamp;        elapsed = TimeSpan.Zero;        total = TimeSpan.Zero;        isrunning = false;    }    internal void Restart()    {        Reset();        Start();    }    internal void Step()    {        if (isrunning)        {            long last = count;            count = Timestamp;            long offset = count - last;            elapsed = DeltaToTimeSpan(offset);            total += elapsed;        }    }    private TimeSpan DeltaToTimeSpan(long delta)    {        return TimeSpan.FromTicks((delta * nsfactor) / Frequency);    }    /*-----------------------------------------------------------------------------*     *******************************************************************************     *-----------------------------------------------------------------------------*/};


Very simple yet effective mechanism, and performance is excellent.
Hi,

Although the proper solution was already given by keinmann, just for the sake of knowledge, here are a couple of points to consider with C# timers:

1. System.Windows.Forms timers are driven by Win32's windows message loop. Basically, a WM_TIMER message is sent to the window procedure and dispatched to the event handler.
This is a low-frequency timer and it also suffers because many other windows messages can be present in the message queue for the main UI thread when it is posted, causing a delay until it actually reaches the application.

2. System.Timers.Timer should be a better solution but it is activated by sending an event from a thread in a ThreadPool provided by OS. How you can draw when you get a callback from a thread that is not the main UI event handling thread is beyond me.

The same applies for System.Threading.Timer, it is called from a ThreadPool and not on the UI thread.

3. You also allocated a thread for drawing. It operates on a graphics object but what thread was the control from which that graphics is derived from ? (unless you're doing something else entirely). Usually you can't do stuff like that if you allocated a Control in thread #1 and you try to change its state from thread #2.

I don't know how it even worked, unless there's something I'm missing.
-----------------------------He moves in space with minimum waste and maximum joyGalactic Conflict demo reel -http://www.youtube.com/watch?v=hh8z5jdpfXY

This topic is closed to new replies.

Advertisement