WPF - Trying to get a timer with an interval of less than ~16ms

Started by
4 comments, last by maya18222 11 years, 3 months ago
I'm trying to devise a way of a getting some kind of timer to signal an event in my WPF application with an interval of around 5ms. My attempts with using a DispatchTimer or a Timer and having the interval set to 1ms, lead to their "tick" events being triggered at only around 60 times a second, which isnt fast enough.

Is there any way I can achieve something faster? either through some event mechanism or by any other means?

I would like something similar to that of a win32 application, as shown below.
int Application::run(){   while(msg.message != WM_QUIT)   {	    if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE )) // If there are Window messages then process them.	    {			 TranslateMessage( &msg );			 DispatchMessage( &msg );	    }	    else // Otherwise, do my work	    {    		    	    }   return (int)msg.wParam;}
I assume this is how WPF is doing it in the background with its Application.Run(Window()) method. Is there anyway of getting some kind of callback on that "else" branch?
Advertisement

Ohai!

First, some background: the DispatchTimer does not create another thread to run its stuff on, it is periodically checked from the WPF UI thread. This is why you can access UI elements directly and not get an exception. Now, the UI thread itself, (and here is where I start assuming stuff without any real base) is running once per rendered frame (running multiple time would be wasteful). Now, the rendering frame, my guess, is vsync ... umm... synchronized. So, no matter how small you'll set the interval of a DispatcherTimer, it won't be checked faster than your refresh rate (which, coincidentally, is almost always 60 hertz).

Now, what I would do: I would start a BackgroundWorker (which creates a different thread) and run an infinite loop in which I do my work and then sleep a bit (5 ms minus the_time_it_got_to_do_my_work_this_frame ). Now all would be nice and well if I wanted to check stuff that is "external" to WPF (like stream some stuff from the HDD, check for a network message, etc).

And from here, trouble: if you would want to access and, heaven forbid, modify UI elements from the secondary thread (our BackgroundWorker), you would have to use Dispatcher.Invoke or Dispatcher.BeginInvoke, and this is where things get messy: Dispatcher.Invoke or Dispatcher.BeginInvoke (msdn it to see the difference or PM me) do their work on the , yes, you guessed it, the Dispatcher thread (in our case the WPF UI one). And, as with everything dispatcher-driven, it is the WPF UI that checks the Invoke or BeginInvoke work queues, yep, you've guessed it, ONCE PER RENDERED FRAME.

So, if you go the BackgroundWorker route BUT you NEED to access or modify at least one UI element EVERY loop, then your update rate will slow down to 60 times per second (if you use beginInvoke this won't happen, but the UI changes will be the same - 60 hertz - in both cases).

My recommendation is (and this is a general WPF recommendation): split the UI logic from the application logic and UPDATE THE UI ONLY WHEN YOU REALLY NEED IT. So you should do your update cycle once every 5 ms, if you have to, but don't touch the UI unless when and if you really need to (and never once per update cycle).

And the real answer: i am unaware of any way to SPEED UP the WPF update cycle (but I guess it's definitely not recommended either way).

Cheers!

I'm not sure, but I think WPF timers use native win32 timers, which, if I remember correctly, are limited to 10-15ms accuracy. "Multiple timer messages get merged into one because they are implemented as a flag in GetMessage()", whatever that means exactly. Alternatives, which I admit I haven't needed to investigate yet, include multimedia timers and waitable timer objects. I remember reading it's actually impossible to get VERY ACCURATE timer event notifications from the OS in Windows in user-mode code. Obviously I'm not certain about anything I've just said wacko.png

I remember reading the same thing on MSDN. There's a high-performance timer, but it's not event based - you just poll it.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

As Amr0 and others have said above, DispatchTimer and System.Threading.Timer et. al. use regular Win32 timers and/or GetTickCount() under the hood and therefore have resolution in the 10-15 millisecond range at best.

If you want better resolution than this you need to use the C# eqivalent of the Win32 call QueryPerformanceCounter(): this is what the C# class System.Diagnostics.Stopwatch() is. However, StopWatch is intended to be used to measure durations -- basically for profiling purposes, which is why it is in the Diagnostics namespace -- not to drive the flow of control of a program with events and/or callbacks like the other kinds of timer.

Thank you for the replies. Im actually trying to come up with a way of updating winForms controls in a wpf application at a rate of several hundred FPS.

My first approach was just


DispatcherTimer dt = new DispatcherTimer();
dt.Interval = TimeSpan.FromMilliseconds(1);
dt.Tick += new EventHandler(OnProcessViewports);
dt.Start();

which as mentioned, gives me arounf 60 FPS.

Ive now tried


Loaded += (s, a) =>
		    {
			    BackgroundWorker bw = new BackgroundWorker();
			    bw.DoWork += (ss, aa) =>
			    {
				    while (true)
				    {
					    Thread.Sleep(16);
					    Dispatcher.Invoke(new EventHandler(OnProcessViewports), new object[] { null, null });
				    }
			    };
			    bw.RunWorkerAsync();
		    };

This works, and gives me several hundred FPS (with a low sleep time), which is what I wanted. But, something odd is happeneing with the CPU usage. If I set the sleep time to about 16ms, then I get around ~55FPS, but, .. the CPU usage goes to about 20-30%.

using the dispatch timer approach as coded above, I get 60 FPS, and around 2-3% CPU usage. Any ideas why this would be the case? Both versions run at around 60FPS, yet the 2nd approach uses a lot more CPU usage.

This topic is closed to new replies.

Advertisement