InvalidOperationException—对象目前在其他地方被使用—红十字会

时间:2021-07-24 21:04:45

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.

我有一个c#桌面应用程序,在这个应用程序中,我不断地创建一个线程,从一个源(实际上是一个数码相机)中获取一个图像,并将它放在一个面板上(面板)。在GUI中(必须是另一个线程,因为它是控件的代码)。

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.

然后面板变成了一个红十字,红色X -我想这是一个无效的图片图标,可以从属性中编辑。应用程序继续工作,但面板永远不会更新。

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

从我可以看出这个错误来自于控制的onpaint事件我在图片上画了别的东西。

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 个解决方案

#1


18  

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:

这是因为Gdi+ Image类不是线程安全的。Hovewer可以避免InvalidOperationException在每次需要图像访问时使用锁,例如绘制或获取图像大小:

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.

如果您使用上述模式,则不需要调用。

#2


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.

这对我的案例和教训是有效的:检查在线程争用发生时使用的所有引用类型。

#3


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.

如果您做不到这一点,请首先将接收到的帧从摄像机复制到一个新的缓冲区,并将该缓冲区附加到队列中,或者使用两个交替缓冲区检查是否有溢出。要么使用myOutPutPanel。BeginInvoke调用camera_ReceivedFrame方法,或者最好有一个线程运行,它检查队列,当它有一个新条目时,它调用mnyOutPutPanel。开始调用方法,将新缓冲区设置为面板上的映像。

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;
    }
}

#4


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

我认为这是多线程问题,使用windows黄金法则并更新主线程使用面板中的面板。调用它应该可以克服交叉线程异常

#1


18  

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:

这是因为Gdi+ Image类不是线程安全的。Hovewer可以避免InvalidOperationException在每次需要图像访问时使用锁,例如绘制或获取图像大小:

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.

如果您使用上述模式,则不需要调用。

#2


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.

这对我的案例和教训是有效的:检查在线程争用发生时使用的所有引用类型。

#3


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.

如果您做不到这一点,请首先将接收到的帧从摄像机复制到一个新的缓冲区,并将该缓冲区附加到队列中,或者使用两个交替缓冲区检查是否有溢出。要么使用myOutPutPanel。BeginInvoke调用camera_ReceivedFrame方法,或者最好有一个线程运行,它检查队列,当它有一个新条目时,它调用mnyOutPutPanel。开始调用方法,将新缓冲区设置为面板上的映像。

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;
    }
}

#4


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

我认为这是多线程问题,使用windows黄金法则并更新主线程使用面板中的面板。调用它应该可以克服交叉线程异常