Reputation: 611
I have a VB.Net and WPF application. One part of the application uses a "image generator" to calculate images. Another is using a "Timer" to show histogram. When using the generator to create about 2 Frames per second, CPU is about zero usage, image is created fine and Histogram will be created in Timer all 1 second. All seems to be PERFECT.
If I let calculate the image by using my own "image generator" with About 20 FPS instead of only 2, CPU is going up to 20% but the Timer never throws an event. Image in GUI is showing 20FPS. If I stop the image generator, immediately the Timer fires his event.
I have no idea where to start to search.
Me.TimerCalc = New System.Windows.Threading.DispatcherTimer
Me.TimerCalc.Interval = TimeSpan.FromMilliseconds(1000)
Me.TimerCalc.Start
Private Sub TimerCalc_Tick(sender As Object, e As System.EventArgs) Handles TimerCalc.Tick
'Does not fire if other process is running by using more CPU
End Sub
If I reduce the framerate to about 10FPS, sometimes the DispatcherTimer throws his event, somethimes not. If I increase the framerate, the DispatcherTimer will throw less events.
Edit Download demo project here: VisualStudio 2010 Dispatcher Test with Windows Form Timer
Edit Video of demo uploaded here: Video where cube does not rotate anymore
Upvotes: 3
Views: 3038
Reputation: 3312
Unfortunately, the DispatcherTimer
cannot fire every 50 milliseconds (20 times a second). Your CPU might show only 20% usage, because you might have more than 1 core, but the WPF GUI thread runs on only 1 core.
To find out how quickly DispatcherTimer
can fire, run it with an DispatcherTimer.Interval
of 1 millisecond and at default priority DispatcherPriority.Background
. Tick gets actually called much slower. On my PC, Tick run every 3..300 milliseconds, even WPF had nothing else to do.
You can increase the priority to DispatcherPriority.Input
, then the Tick run every 4...125 milliseconds. You could use a higher priority, but then the rendering stops running smoothly.
Even if you chose an Interval of 200 milliseconds and count them, you will find that some Ticks get lost over time. You can improve that by measuring for each Tick how many milliseconds it was delayed and shorten the Interval accordingly.
For details, see my article on CodeProject: Improving the WPF DispatcherTimer Precision
Upvotes: 0
Reputation: 611
The Dispatcher is blocked when updating the Image. Unblock by using an new Thread.
Private Sub cImageGenerator_NewImageAvailable() Handles cImageGenerator.NewImageAvailable
If _IsReady = False Then Exit Sub
_IsReady = False
' ################
' # Thread - Christian Zech
' #################
Dim trd As New System.Threading.Thread(Sub()
' ##########################
' # Clone
' ##########################
Dim bm As System.Drawing.Bitmap = Me.cImageGenerator.CreateBitmap '.Clone
Dim imgSrc As ImageSource = BitmapToWpfBitmapSource(bm)
imgSrc.Freeze() 'needed because otherwise we will get the error: The calling thread cannot access this object because a different thread owns it.
Me.Dispatcher.Invoke(New Action(Of ImageSource)(AddressOf UpdateImage), imgSrc)
End Sub)
trd.SetApartmentState(System.Threading.ApartmentState.STA) ''ToDo: MTA probieren
trd.Name = "cImageGenerator_NewImageAvailable"
trd.Start()
End Sub
Private Sub UpdateImage(ByVal img As ImageSource)
Me.img1.Source = img
_IsReady = True
End Sub
Upvotes: 0
Reputation: 15237
I can't duplicate this problem on my end. It works fine for me.
I do wonder what you might be doing in the TimerCalc_Tick function, though. Anything you do in there will block the Dispatcher. The event won't fire again until that one is completed. So I could definitely see that screwing something up.
After looking at the code some more, and looking at the video, and playing with it on my machine, I think you're just starving the Dispatcher when you get up to the higher rates. I'm not sure why the rectangle completely stops rotating for you (it doesn't do that for me), but if your processor/graphics card is lower than mine, I could see that being the difference. When I run the slider all the way to the left like you did in your video, my processor (all four cores) is pegged at about 40%. A lower-end machine would be worse than that and could conceivably be starving other threads.
If you take the project as it is posted above, and you just make one single change, turning this:
Public Sub New()
Me.TimerRefresh = New System.Windows.Forms.Timer
Me.TimerRefresh.Interval = Me.Intervall
Me.TimerRefresh.Enabled = True
End Sub
into this:
Public Sub New()
Me.TimerRefresh = New System.Windows.Forms.Timer
Me.TimerRefresh.Interval = Me.Intervall
Me.TimerRefresh.Enabled = False
End Sub
so that you don't actually start the timer that takes all the processing, and then you run the app and run the slider all the way to the left, does the rectangle still stop? If it doesn't, then I'm confident you're just starving the Dispatcher. If it does... then I'm still confused.
Upvotes: 3
Reputation: 18068
If you want short and steady intervals use multimedia timer instead (find dot net wrapper on code project site).
If you need to do work on UI's thread, call a dispatcher (not dispatcher timer) on multimedia timer's event. Set dispatcher priority to input to make sure UI does not block it. Make sure operations take much less than the interval.
Intervals of 5mSec and above are recommended.
Upvotes: 0