InvalidOperationException - 对象当前正在其他地方使用 - 红叉

时间:2009-06-29 20:16:04

标签: c# winforms multithreading gdi+ invalidoperationexception

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

该应用程序可以正常运行,但在某些机器上,我会以随机的时间间隔(不可预测的)获得以下错误

************** Exception Text **************
System.InvalidOperationException: The object is currently in use elsewhere. 

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

据我所知,这个错误来自控件的onpaint事件,我在图片上画了其他东西。

我尝试使用锁而没有运气:(

我调用将图像放在面板上的函数的方式如下:

if (this.ReceivedFrame != null)
{
    Delegate[] clients = this.ReceivedFrame.GetInvocationList();
    foreach (Delegate del in clients)
    {
        try
        {
            del.DynamicInvoke(new object[] { this, 
                new StreamEventArgs(frame)} );
        }
        catch { }
    }
}

这是代表:

public delegate void ReceivedFrameEventHandler(object sender, StreamEventArgs e);
    public event ReceivedFrameEventHandler ReceivedFrame;

这就是控制代码隐藏中的函数如何注册它:

Camera.ReceivedFrame += 
    new Camera.ReceivedFrameEventHandler(camera_ReceivedFrame);

我也试过

del.Method.Invoke(del.Target, new object[] { this, new StreamEventArgs(b) });

而不是

del.DynamicInvoke(new object[] { this, new StreamEventArgs(frame) });

但没有运气

有没有人知道如何修复此错误或至少以某种方式捕获错误并让线程再次将图像放在面板上?

4 个答案:

答案 0 :(得分:20)

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

Image DummyImage;

// Paint
lock (DummyImage)
    e.Graphics.DrawImage(DummyImage, 10, 10);

// Access Image properties
Size ImageSize;
lock (DummyImage)
    ImageSize = DummyImage.Size;

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

答案 1 :(得分:5)

我遇到了类似的错误消息但是尽可能地尝试,锁定位图并没有为我解决任何问题。然后我意识到我正在使用静态画笔绘制一个形状。果然,正是刷子引起了线程争用。

var location = new Rectangle(100, 100, 500, 500);
var brush = MyClass.RED_BRUSH;
lock(brush)
    e.Graphics.FillRectangle(brush, location);

这适用于我的案例和经验教训:检查在发生线程争用时使用的所有引用类型。

答案 2 :(得分:2)

在我看来,多次使用相同的Camera对象。

E.g。尝试为每个接收到的帧使用新缓冲区。在我看来,当图片框正在绘制新框架时,您的捕获库会再次填充该缓冲区。因此,在速度更快的机器上,这可能不是问题,对于速度较慢的机器,这可能是一个问题。

我已经编程了类似的东西一次,在每个接收到的帧之后,我们不得不请求接收下一帧并在该请求中设置 NEW 帧接收缓冲区。

如果您不能这样做,请将收到的帧首先从相机复制到新缓冲区并将该缓冲区附加到队列中,或者只使用2个交替缓冲区并检查溢出。使用myOutPutPanel.BeginInvoke调用camera_ReceivedFrame方法,或者更好地运行一个线程,它检查队列,当它有一个新条目时,它调用mnyOutPutPanel.BeginInvoke来调用你的方法将新缓冲区设置为面板上的图像。 / p>

此外,一旦收到缓冲区,使用Panel Invoke方法调用图像设置(保证它在窗口线程中运行,而不是从捕获库中运行的线程)。

可以从任何线程(捕获库或其他单独的线程)调用以下示例:

void camera_ReceivedFrame(object sender, StreamEventArgs e)
{
    if(myOutputPanel.InvokeRequired)
    {
        myOutPutPanel.BeginInvoke( 
            new Camera.ReceivedFrameEventHandler(camera_ReceivedFrame), 
            sender, 
            e);
    }
    else
    {
        myOutPutPanel.Image = e.Image;
    }
}

答案 3 :(得分:0)

我认为这是多线程问题 使用windows golden rule并更新主线程使用面板中的面板.Invoke 这应该克服交叉线程异常