30

I have a C# desktop application in which one thread that I create continously gets an image from a source(it's a digital camera actually) and puts it on a panel(panel.Image = img) in the GUI(which must be another thread as it is the code-behind of a control.

The application works but on some machines I get the following error at random time intervals(unpredictable)

************** Exception Text **************
System.InvalidOperationException: The object is currently in use elsewhere. 

Then the panel turns into a red cross, red X - i think this is the invalid picture icon that is editable from the properties. The application keeps working but the panel is never updated.

From what I can tell this error comes from the control's onpaint event where I draw something else on the picture.

I tried using a lock there but no luck :(

The way I call the function that puts the image on the panel is as follows:

if (this.ReceivedFrame != null)
{
    Delegate[] clients = this.ReceivedFrame.GetInvocationList();
    foreach (Delegate del in clients)
    {
        try
        {
            del.DynamicInvoke(new object[] { this, 
                new StreamEventArgs(frame)} );
        }
        catch { }
    }
}

this is the delegate:

public delegate void ReceivedFrameEventHandler(object sender, StreamEventArgs e);
    public event ReceivedFrameEventHandler ReceivedFrame;

and this is how the function inside the control code-behind registers to it:

Camera.ReceivedFrame += 
    new Camera.ReceivedFrameEventHandler(camera_ReceivedFrame);

I also tried

del.Method.Invoke(del.Target, new object[] { this, new StreamEventArgs(b) });

instead of

del.DynamicInvoke(new object[] { this, new StreamEventArgs(frame) });

but no luck

Does anyone know how I could fix this error or at least catch the error somehow and make the thread put the images on the panel once again?

4 Answers4

20

This is because Gdi+ Image class is not thread safe. Hovewer you can avoid InvalidOperationException by using lock every time when you need to Image access, for example for painting or getting image size:

Image DummyImage;

// Paint
lock (DummyImage)
    e.Graphics.DrawImage(DummyImage, 10, 10);

// Access Image properties
Size ImageSize;
lock (DummyImage)
    ImageSize = DummyImage.Size;

BTW, invocation is not needed, if you will use the above pattern.

arbiter
  • 9,123
  • 1
  • 30
  • 41
  • well my function that runs inside the onpaint event draws a lot of stuff onto the panel above the image that the other thread sets on the panel so how can I lock everithing that is drawn? this includes rectangles, lines and images –  Jun 29 '09 at 20:41
  • I tied locking the panel but it did not work I still got the error –  Jun 29 '09 at 20:42
  • You do not need to lock panel, you need to lock particular image you work with. – arbiter Jun 29 '09 at 20:52
  • I wonder what would happen if he was to call `LockBits()` and then `UnlockBits()` and do the rendering in between. – Dmitri Nesteruk Dec 27 '09 at 20:14
5

I had a similar problem with the same error message but try as I might, locking the bitmap didn't fix anything for me. Then I realized I was drawing a shape using a static brush. Sure enough, it was the brush that was causing the thread contention.

var location = new Rectangle(100, 100, 500, 500);
var brush = MyClass.RED_BRUSH;
lock(brush)
    e.Graphics.FillRectangle(brush, location);

This worked for my case and lesson learned: Check all the reference types being used at the point where thread contention is occurring.

Nick Gotch
  • 8,739
  • 13
  • 66
  • 94
  • 1
    In my case it was the brush as well. Just for fun I tried it with a font but that seems to work from multiple threads. – Alois Kraus May 16 '16 at 19:43
2

Seems to me, that the same Camera object is used several times.

E.g. try to use a new buffer for each received frame. It seems to me, that while the picture box is drawing the new frame, your capture library fills that buffer again. Therefore on faster machines this might not be an issue, with slower machines it might be an issue.

I've programmed something similar once, after each received frame, we had to request to receive the next frame and set the NEW frame receive buffer in that request.

If you can not do that, copy the received frame from the camera first to a new buffer and append that buffer to a queue, or just use 2 alternating buffers and check for overruns. Either use myOutPutPanel.BeginInvoke to call the camera_ReceivedFrame method, or better have a thread running, which checks the queue, when it has a new entry it calls mnyOutPutPanel.BeginInvoke to invoke your method to set the new buffer as image on the panel.

Furthermore, once you received the buffer, use the Panel Invoke Method to invoke the setting of the image (guarantee that it runs in the window thread and not the thread from your capture library).

The example below can be called from any thread (capture library or other separate thread):

void camera_ReceivedFrame(object sender, StreamEventArgs e)
{
    if(myOutputPanel.InvokeRequired)
    {
        myOutPutPanel.BeginInvoke( 
            new Camera.ReceivedFrameEventHandler(camera_ReceivedFrame), 
            sender, 
            e);
    }
    else
    {
        myOutPutPanel.Image = e.Image;
    }
}
Dennis Kuhn
  • 207
  • 2
  • 10
0

I think this is multithreading problem Use windows golden rule and update the panel in the main thread use panel.Invoke This should overcome cross threading exception

Ahmed Said
  • 6,813
  • 10
  • 52
  • 92
  • I am updating the panel in the main thread but I am calling the function that updates it from the other thread and passing the image as a parameter. –  Jun 29 '09 at 20:39
  • if you call the function that updates the panel from another thread and in the function itself there is no context switching (switch to the main thread using invoke for example) this means your update done on the other thread not the main thread – Ahmed Said Jun 30 '09 at 07:19