I need to obtain a lock in two different threads in order to access a Bitmap (which is populated from a webcam) in EmguCv.
I have a "GetFrame" functions that queries the camera and places what it returns into a .NET Bitmap. I have two threads which need to access this Bitmap, one needs to write to the Bitmap and assign the Bitmap to a picture box, and the other needs to read the Bitmap, convert it to an Image object and assign that to an EMGU ImageBox.
I first lock on an arbitrary Object, then I do my operations. The code is as follows (_Camera.LiveFrame is the Bitmap):
Writing/Reading Thread:
while (_CaptureThreadRunning)
{
lock (_Camera)
{
// _Camera.GetFrame writes to the Bitmap
if (_VideoPlaying && _Camera.GetFrame(500))
pbLiveFeed.Invalidate();
}
}
_Camera.CloseCamera(true);
_CaptureExitEvent.Set(); // Set to signal captureThread has finished
Reading/ImageBox Thread:
while (_ProcessThreadRunning)
{
lock (_Camera)
{
// _Camera.LiveFrame is the Bitmap
procImage = new Image<Bgr, int>((Bitmap)_Camera.LiveFrame.Clone());
procImage.Draw(new Rectangle(10,20,20,15),new Bgr(Color.LightGreen), 5);
ibProcessed.Image = procImage;
ibProcessed.Invalidate();
}
}
_ProcessExitEvent.Set();
This runs fine most of the time but every now and then I get a "Object is in use elsewhere" error when I try to Clone() the Bitmap. Is this not the proper way to lock? I don't see why this would cause a problem.
ps. My threads can no longer exit gracefully either. My .Set() calls outside of my loops are never called. I am guessing the threads are deadlocked?
GDI+ has a locking mechanism which prevents two threads from using a Bitmap object--this is the error you're receiving.
You are trying to access the bitmap while the UI thread is already accessing it. For example, 1) you assign the bitmap to the picture box, 2) the picture box invalidates and then repaints, 3) you exit the write/read thread lock, and then 4) the reading/imagebox thread is trying to access the same Bitmap while the repainting is still occurring.
To solve the problem, just make a copy of the bitmap, and use that copy to manipulate. Whatever you give the picture box, don't assume you can touch that again from a non-UI thread.
For example, in _Camera.GetFrame:
// Get the bitmap from the camera
capturedBitmap = GetFromCamera();
// Clone the bitmap first before assigning to the picture box
_Camera.LiveFrame = new Bitmap(capturedBitmap);
// Assign to the picture box
pbLiveFeed.Image = capturedBitmap;
Now, _Camera.LiveFrame should be accessible from the thread, as long as you have proper locking.
A couple of other issues I would think about here:
You mention you are locking on an "arbitrary object", but _Camera seems to be anything but that--it is an object which could be used elsewhere in unpredictable ways. I would suggest making an object which is only used for locking, e.g.
object lockObject = new lockObject;
lock (lockObject)
{
// put your synchronized code here
}
Bitmap.Clone() creates a bitmap which shares pixel data with the original bitmap. When you convert to the image object to assign to the EMGU ImageBox, you are using that clone, which maintains a reference to the bitmap. So, it seems safer to me to just create a new bitmap, rather than to use Clone() in this case.
I think that you can avoid using explicit locks here at all. Just move the bitmap creation operation to the receiving thread - this way you will guarantee that all operations on the original bitmap are executed from the receiving thread.
Once bitmap creation is done, pass a reference to the new bitmap to the reading thread - assign it to a member of the class servicing it. Reference assignment is an atomic operation, you are guaranteed that the reading thread will either see the new value or the old one. And whereas you only pass the reference on after you are done creating bitmap, you are guaranteed that only the reading thread will ever work with it
You can use ManualResetEvent instead locks to orchestrate the read operations and write.
An example would be something like this.
Writing/Reading Thread:
while (_CaptureThreadRunning)
{
imageBoxTrhead.WaitOne();
readWriteThread.Reset();
// _Camera.GetFrame writes to the Bitmap
if (_VideoPlaying && _Camera.GetFrame(500))
pbLiveFeed.Invalidate();
readWriteThread.Set();
}
_Camera.CloseCamera(true);
_CaptureExitEvent.Set();
Reading/ImageBox Thread:
while (_ProcessThreadRunning)
{
readWriteThread.WaitOne();
imageBoxTrhead.Reset();
// _Camera.LiveFrame is the Bitmap
procImage = new Image<Bgr, int>((Bitmap)_Camera.LiveFrame.Clone());
procImage.Draw(new Rectangle(10,20,20,15),new Bgr(Color.LightGreen), 5);
imageBoxTrhead.Set();
ibProcessed.Image = procImage;
ibProcessed.Invalidate();
}
_ProcessExitEvent.Set();
Where readWriteThread and imageBoxTrhead are ManualResetEvent objects by default signaled.