Problem rendering graphics with a BackgroundWorker
I am trying to make a graphics program with mousewheel-controlled zooming using GDI+. My plan is to provide rapid feedback, such as a low resolution render or even just a bounding rectangle, followed by a full resolution render as soon as possible afterwards. It seems logical to me to try a BackgroundWorker for the latter task. This is my first attempt to use one of these, or any other way of multithreading for that matter.
Mousewheel events can follow one another much more quickly than I can render to the screen. That means I need to kill any rendering process that is already running and start a new one with the latest value of the zoom scaling factor. Here is a stripped down version of my code.
vb.net Code:
Imports System.ComponentModel
Public Class ZoomWorker
Public Event ProvideFeedBack()
Public Event ScaledImageReady()
Public SourceImage As Bitmap
Public ScaledImage As Bitmap
Private _ScaleFactor As Single = 1.0
Private WithEvents ScaleImageWorker As New BackgroundWorker
Public Sub New()
ScaleImageWorker.WorkerSupportsCancellation = True
AddHandler DrawingSurface.MouseWheelTurned, AddressOf SetScale
End Sub
Private Sub SetScale(ByVal sender As DrawingSurface, ByVal e As MouseEventArgs)
'calculate the new scale factor
_ScaleFactor *= CSng(1 + e.Delta * (1 + e.Delta / 120) / 20000)
RaiseEvent ProvideFeedBack()
If ScaleImageWorker.IsBusy Then
ScaleImageWorker.CancelAsync()
'this is where it hangs:
Do Until ScaleImageWorker.CancellationPending = False
Threading.Thread.Sleep(10)
Loop
End If
ScaleImageWorker.RunWorkerAsync(_ScaleFactor)
End Sub
Private Sub ScaleImageWorker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles ScaleImageWorker.DoWork
If e.Cancel Then Exit Sub
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
e.Result = ScaleImage(CSng(e.Argument), ScaleImageWorker, e)
End Sub
Private Sub ScaleImageWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles ScaleImageWorker.RunWorkerCompleted
ScaledImage = DirectCast(e.Result, Bitmap)
RaiseEvent ScaledImageReady()
End Sub
Private Function ScaleImage(ByVal scalefactor As Single, _
ByVal worker As BackgroundWorker, ByVal e As DoWorkEventArgs) As Bitmap
If worker.CancellationPending Then
e.Cancel = True
Else
'render and return the scaled bitmap
End If
End Function
The above class coexists with a DrawingSurface class, a UserControl which listens for the ProvideFeedback and ScaledImageReady events and paints the image accordingly. It also raises mouse/keyboard events for the zoom and other tools to deal with.
My problem arises in the DO loop waiting for Cancellation to finish. The zooming routine works for one or maybe half a dozen renders, depending on the image size, but then it hangs in that loop and never comes out. Can anyone advise me how to fix it?
Of course, if there's a better alternative approach I'd also be glad to hear about it. I'm not yet ready to take on WPF, unfortunately.
cheers, BB
Re: Problem rendering graphics with a BackgroundWorker
Are you ever setting CancellationPending to False explicitly? Not that I can see. As far as I'm aware it wouldn't get reset to False implicitly until you call RunWorkerAsync again, which you don't do until CancellationPending is False. Deadlock!
What I think you need to do is test IsBusy in that loop rather than CancellationPending.
Re: Problem rendering graphics with a BackgroundWorker
Quote:
Originally Posted by
jmcilhinney
Are you ever setting CancellationPending to False explicitly?
I don't know how to do that other than by CancelAsync. CancellationPending is read only.
I tried replacing the loop by this:
Code:
Do Until ScaleImageWorker.IsBusy = False
ScaleImageWorker.CancelAsync()
Loop
but the effect was identical. Then I had another idea and tried replacing the loop by this:
Code:
ScaleImageWorker.CancelAsync()
ScaleImageWorker = New BackgroundWorker
ScaleImageWorker.WorkerSupportsCancellation = True
ScaleImageWorker.RunWorkerAsync(_ScaleFactor)
Amazingly, it does work. I wish I understood why, but at least now I can zoom. However, I am getting fairly frequent "Object is in use elsewhere" errors, mainly in the Paint event of the DrawingSurface which uses ScaledImage. I'm going to have to look at that tomorrow but you may hear me pleading for assistance again.
Thanks very much for your help so far.
regards, BB