30

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

4 回答 4

20

这是因为 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;

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

于 2009-06-29T20:29:11.090 回答
5

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

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

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

于 2013-01-05T23:03:56.930 回答
2

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

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

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

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

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

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

void camera_ReceivedFrame(object sender, StreamEventArgs e)
{
    if(myOutputPanel.InvokeRequired)
    {
        myOutPutPanel.BeginInvoke( 
            new Camera.ReceivedFrameEventHandler(camera_ReceivedFrame), 
            sender, 
            e);
    }
    else
    {
        myOutPutPanel.Image = e.Image;
    }
}
于 2012-03-18T19:48:07.757 回答
0

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

于 2009-06-29T20:27:35.260 回答