在线程之间锁定图像的麻烦

我需要在两个不同的线程中获取一个锁,以便访问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((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时,我偶尔会得到一个“对象在其他地方使用”的错误。 这不是正确的锁定方式吗? 我不明白为什么这会导致问题。

PS。 我的线程也不能再优雅地退出。 永远不会调用我的循环之外的.Set()调用。 我猜测线程是死锁的?

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。

我想在这里讨论的其他几个问题:

  1. 你提到你锁定了一个“任意对象”,但是_Camera似乎不是那个 – 它是一个可以在其他地方以不可预测的方式使用的对象。 我建议制作一个仅用于锁定的物体,例如

     object lockObject = new lockObject; lock (lockObject) { // put your synchronized code here } 
  2. Bitmap.Clone()创建一个位图,该位图与原始位图共享像素数据。 当您转换为要分配给EMGU ImageBox的图像对象时,您正在使用该克隆,该克隆维护对位图的引用。 因此,对我来说,创建一个新的位图似乎更安全,而不是在这种情况下使用Clone()。

我认为你可以避免在这里使用显式锁。 只需将位图创建操作移动到接收线程 – 这样就可以保证原始位图上的所有操作都是从接收线程执行的。

完成位图创建后,将对新位图的引用传递给读取线程 – 将其分配给为其提供服务的类的成员。 引用赋值是一个primefaces操作,保证读取线程将看到新值或旧值。 虽然您只在完成创建位图后传递引用,但您可以保证只有读取线程才能使用它

您可以使用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((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是默认信号的M​​anualResetEvent对象。