【问题标题】:InvalidOperationException - object is currently in use elsewhere - red crossInvalidOperationException - 对象当前正在其他地方使用 - 红十字
【发布时间】:2010-11-06 19:30:36
【问题描述】:

我有一个 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) });

但没有运气

有谁知道我可以如何修复这个错误,或者至少以某种方式捕获错误并让线程再次将图像放在面板上?

【问题讨论】:

    标签: c# winforms multithreading gdi+ invalidoperationexception


    【解决方案1】:

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

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

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

    【讨论】:

    • 就我而言,它也是刷子。只是为了好玩,我尝试了一种字体,但这似乎可以在多个线程中使用。
    【解决方案2】:

    在我看来,同一个 Camera 对象被多次使用。

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

    我曾经编写过类似的程序,在每个接收到的帧之后,我们必须请求接收下一帧并在该请求中设置 NEW 帧接收缓冲区。

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

    此外,一旦您收到缓冲区,使用 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】:

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

      Image DummyImage;
      
      // Paint
      lock (DummyImage)
          e.Graphics.DrawImage(DummyImage, 10, 10);
      
      // Access Image properties
      Size ImageSize;
      lock (DummyImage)
          ImageSize = DummyImage.Size;
      

      顺便说一句,如果您将使用上述模式,则不需要调用。

      【讨论】:

      • 好吧,我在 onpaint 事件中运行的函数会在面板上其他线程在面板上设置的图像上方的面板上绘制很多东西,那么我如何才能锁定绘制的所有内容?这包括矩形、线条和图像
      • 我绑定锁定面板但它不起作用我仍然收到错误
      • 您不需要锁定面板,您需要锁定您使用的特定图像。
      • 我想知道如果他调用LockBits() 然后UnlockBits() 并在两者之间进行渲染会发生什么。
      【解决方案4】:

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

      【讨论】:

      • 我正在更新主线程中的面板,但我正在调用从另一个线程更新它的函数并将图像作为参数传递。
      • 如果您调用从另一个线程更新面板的函数并且在函数本身中没有上下文切换(例如使用调用切换到主线程),这意味着您在另一个线程上完成了更新不是主线程
      猜你喜欢
      • 2012-12-04
      • 1970-01-01
      • 2020-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多