我需要在两个不同的线程中获取一个锁,以便访问EmguCv中的Bitmap(从网络摄像头填充)。 我有一个“GetFrame”函数,用于查询相机并将其返回到.NET位图中。我有两个需要访问此Bitmap的线程,一个需要写入Bitmap并将Bitmap分配给一个图片框,另一个需要读取Bitmap,将其转换为Image对象并将其分配给EMGU ImageBox。 我先锁定一个任意对象,然后进行操作。代码如下(_Camera.LiveFrame是Bitmap):
写作/阅读主题:
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
阅读/ ImageBox线程:
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();
这在大多数情况下运行正常,但是当我尝试Clone()Bitmap时,我偶尔会得到“Object is in others others”错误。这不是正确的锁定方式吗?我不明白为什么这会导致问题。
PS。我的线程也不能再优雅地退出。永远不会调用我的循环之外的.Set()调用。我猜测线程已陷入僵局?
答案 0 :(得分:3)
GDI +有一个锁定机制,可防止两个线程使用Bitmap对象 - 这是您收到的错误。
您正在尝试在UI线程已访问位图时访问位图。例如,1)您将位图分配给图片框,2)图片框无效然后重新绘制,3)退出写/读线程锁,然后4)读/图像线程试图访问它重绘时仍然发生位图。
要解决此问题,只需复制位图,然后使用该副本进行操作。无论你给图片框什么,不要以为你可以从非UI线程再次触摸它。
例如,在_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;
现在,只要你有正确的锁定,就可以从线程访问_Camera.LiveFrame。
我想在这里讨论其他几个问题:
你提到你正在锁定一个“任意对象”,但是_Camera似乎不是那个 - 它是一个可以在其他地方以不可预测的方式使用的对象。我建议制作一个仅用于锁定的物体,例如
object lockObject = new lockObject;
lock (lockObject)
{
// put your synchronized code here
}
Bitmap.Clone()创建一个与原始位图共享像素数据的位图。当您转换为要分配给EMGU ImageBox的图像对象时,您正在使用该克隆,该克隆维护对位图的引用。因此,对我来说,创建一个新的位图似乎更安全,而不是在这种情况下使用Clone()。
答案 1 :(得分:0)
我认为你可以避免在这里使用显式锁。只需将位图创建操作移动到接收线程 - 这样就可以保证原始位图上的所有操作都是从接收线程执行的。
完成位图创建后,将对新位图的引用传递给读取线程 - 将其分配给为其提供服务的类的成员。引用赋值是一个原子操作,保证读取线程将看到新值或旧值。虽然您只在完成创建位图后传递引用,但您可以保证只有读取线程才能使用它
答案 2 :(得分:0)
您可以使用ManualResetEvent代替锁定来协调读取操作和写入。 一个例子是这样的。
写作/阅读主题:
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();
阅读/ ImageBox线程:
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();
默认情况下,readWriteThread和imageBoxTrhead是ManualResetEvent对象的信号。