views:

166

answers:

4

I created an image-viewing control for my boss that incorporates panning, zooming via the mouse wheel, and drawing a box to zoom to. The control needs to support very large image files (i.e. several thousand pixels on each side).

It all works, but whenever the code is scaling the image the control UI becomes unresponsive. My boss had me use threading to set the scaling code apart from the UI. The scaling code is now definitely on a separate thread but the UI is still bogged down while scaling code is running! Can anyone please help me with this??

Below is the Scaling code. Let me know if this isn't enough to help me and I'll post whatever code you need!

UPDATE: Here's the control code in its entirety. link text

A: 

I suspect that since the scaling is using all the CPU that the UI is bogging. Both threads are probably at the same priority.

Try lowering the priority of the scaling thread to allow the UI to be responsive.

Values for Thread Priority:

Above Normal -> Gives thread higher priority
Below Normal ->Gives thread lower priority
Normal -> Gives thread normal priority
Lowest -> Gives thread lowest priority
Highest -> Gives thread highest priority

so you would probably use:

Thread1.Priority=System.Threading.ThreadPriority.BelowNormal
gbrandt
I somehow doubt that's the case. I think most likely the invocation is the problem.
Miky Dinescu
In the sub that creates and starts the subroutine above, I already set the priority to BelowNormal. It makes no difference if I set the priority to Lowest.
Ski
Ski, please provide details about how you're invoking this method asynchronously. That way we'll be able to help!
Miky Dinescu
@Miky D: done! see above.
Ski
A: 

Try using this instead.

Private Sub Worker(object o)
    Dim args as ScaleImageArguments
    args = CType(o, ScaleImageArguments) 
    ScaleImage(args)
End Sub

Private Sub RunScaleImageAsync(ByVal img As Image, ByVal scale As Double)
    System.Threading.ThreadPool.QueueUserWorkItem(Worker, _
                 New ScaleImageArguments(img.Clone, scale))
End Sub

ALTERNATIVE - Using the Async pattern.

Private Delegate Sub ScaleImageDelegate(ByRef arg As ScaleImageArguments)

Private Sub BeginScaleImage(ByRef img As Image, ByVal scale As Double)
    Dim d As ScaleImageDelegate
    d = New ScaleImageDelegate(AddressOf ScaleImage)

    d.BeginInvoke(New ScaleImageArguments(img.Clone, scale), _
                  New AsyncCallback(AddressOf EndScaleImage), d)        
End Sub

Private Sub EndScaleImage(ar As IAsyncResult)
    Dim d As ScaleImageDelegate
    d = CType(ar.AsyncState, ScaleImageDelegate)
    d.EndInvoke(ar)
End Sub

Then just call BeginScaleImage to run it asynchronously.

EDIT - Please see corrections above. The ar argument on the EndScaleImage should be declared ByRef, and also the img param of the BeginScaleImage. There is no reason why they should be passed ByVal!!

Miky Dinescu
Just tried it, unfortunately it didn't make a difference.
Ski
Have you tried the second option too?
Miky Dinescu
I tried the second option. With img as ByVal in BeginScaleImage there was no difference. With img as ByRef the code gets caught in an endless loop while the image is loading.
Ski
Then the problem is most likely as @Kevin suggested in a comment on a different answer with the way you're "waiting" for the scaled image to be ready. I've looked at the Paint event handler but it's not very clear where those variables are coming from - CurrentScaleImage and the mouse_down, mouse_end..
Miky Dinescu
I don't think that's the problem, though I won't say it's impossible. Should I just post the entire control code for you to loot at?
Ski
I don't think that's the problem, though I won't say it's impossible. Should I just post the entire control code for you to look at?
Ski
I think that would be helpful. There is definitely something that has escaped us so far but unfortunately I have to get going for the day. If you don't get the answer until tomorrow I'll be happy to take a look at the code. It's an interesting problem!
Miky Dinescu
A: 

How is your ImageScaled() diplaying the image back to the UI?

hipplar
The control has a Dictionary of images called ScaledImageHash which uses the scale of the image (a Double) as the key. The control's Paint sub then paints the image in ScaledImageHash that corresponds to the current image scale.The Dictionary allows me to 1) Cache previously scaled images for future use, and more importantly 2) Gives me the ability to preemptively scale the image based on the potential future scale when the user zooms with the mouse wheel. This works, but every time I zoom the image, the control preemptively scales the image again, and the UI bogs down.
Ski
Could the paint be bogged down waiting? You say you have a dictionary, which is fine, but what is the UI displaying while WAITING for the scaling to happen? Does it wait in the paint thread until the scaling is done, or does it just return and NOT paint until the scaling is finished? If it's the first, even if done in another thread it's still "serial" and thus threading gets you nothing. The second requires your callback to then paint it.
Kevin
@Kevin you may be right. I thought he was using the ImageScaled event to know when the operation has completed - although your scenario would explain why his threading is not yielding the expected results.
Miky Dinescu
I just updated with the paint sub. CurrentScaledImage = ScaledImageHash(CurrentImageScale). As you can see, if the current scaled image is not found the paint sub just doesn't try to paint it. The ImageScaled event at the end of the ScaleImage sub calls Invalidate.
Ski
A: 

It appears that there is some global lock within the GDI+ API. For test I created two threads based on following function

static void test_thread()
{
    Bitmap bmp = new Bitmap( 4000, 4000 );
    Graphics g = Graphics.FromImage( bmp );
    Brush b = Brushes.Red ;

    for ( ; ; ) {
        g.FillRectangle( b, 0, 0, bmp.Width, bmp.Height );
    }
}

If the infinite loop has ben left empty, the CPU usage was over 90% so they they utilized both cores of my CPU. With the FillRectangle present, the usage was slightly bellow 50% indicating that only one thread can run it at time.

So it is possible that any GDI+ call you do from the GUI thread while the scaling is in progress will block until the scale completes.

Komat
You are correct! I tried replacing any code in the threaded function that uses Graphics with a long-running, empty for loop and the threading works as intended--tho my control now shows an empty box without the Graphics code. Now the question is, how can I bypass that GDI+ blocking? Am I going to have to write my own scaling function?
Ski
You might try to use the old GDI using the pinvoke mechanism. The problem is that you will not have control about quality of the scaling and there might be other limitations. Other than that I believe that you will have to write your own scaler or find existing one.
Komat