在线程之间锁定映像时出现问题
本文关键字:问题 映像 线程 之间 锁定 | 更新日期: 2023-09-27 17:56:40
我需要在两个不同的线程中获取一个锁才能访问EmguCv中的位图(从网络摄像头填充)。我有一个"GetFrame"函数,用于查询相机并将其返回的内容放入 .NET 位图中。我有两个线程需要访问此位图,一个需要写入位图并将位图分配给图片框,另一个需要读取位图,将其转换为图像对象并将其分配给EMGU图像框。我首先锁定一个任意对象,然后执行操作。代码如下(_Camera.LiveFrame 是位图):
写作/阅读线程:
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
阅读/图像框线程:
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();
这在大多数情况下运行良好,但是当我尝试克隆()位图时,我时不时地收到"对象正在其他地方使用"错误。这不是正确的锁定方式吗?我不明白为什么这会导致问题。
附言。我的线程也无法再正常退出。我。在我的循环之外的 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应该可以从线程访问,只要你有正确的锁定。
我会在这里考虑其他几个问题:
- 你
提到你锁定了一个"任意对象",但_Camera似乎并非如此——它是一个可以在其他地方以不可预测的方式使用的对象。 我建议制作一个仅用于锁定的对象,例如
object lockObject = new lockObject; lock (lockObject) { // put your synchronized code here }
Bitmap.Clone() 创建一个位图,该位图与原始位图共享像素数据。 当您转换为要分配给 EMGU ImageBox 的图像对象时,您正在使用该克隆,该克隆维护对位图的引用。 因此,对我来说,创建一个新的位图似乎更安全,而不是在这种情况下使用 Clone()。
我认为您可以在这里完全避免使用显式锁。只需将位图创建操作移动到接收线程 - 这样您就可以保证原始位图上的所有操作都是从接收线程执行的。
完成位图创建后,将对新位图的引用传递给读取线程 - 将其分配给为其提供服务的类的成员。引用赋值是一种原子操作,您可以保证读取线程将看到新值或旧值。虽然您仅在完成创建位图后传递引用,但可以保证只有读取线程才能使用它
您可以使用 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();
阅读/图像框线程:
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 对象。