You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调整窗口时Aforge PictureBox抛出System.InvalidOperationException异常求助

AForge PictureBox 多线程异常及程序关闭崩溃问题解决

问题现象

调整或移动应用程序窗口时,AForge的PictureBox控件抛出System.InvalidOperationException: 'The object is currently in use elsewhere.'异常,部分场景提示多线程使用问题;使用Invoke修复窗口操作异常后,程序关闭时会崩溃。

异常信息

异常截图

原核心代码

public partial class MainForm : Form
{
    //Global Var
    String[] serialLog = new String[300];
    SerialLogDisplay sld_form;

    FilterInfoCollection filterInfoCollection;
    VideoCaptureDevice videoCaptureDevice;

    public MainForm()
    {
        InitializeComponent();
    }

    //Generated Methods
    private void btnCCamera_Click(object sender, EventArgs e)
    {
        if(cmbCameraS.SelectedIndex > 0) {
            disconnectVideoStream(false);
            videoCaptureDevice = new VideoCaptureDevice(filterInfoCollection[cmbCameraS.SelectedIndex-1].MonikerString);
            videoCaptureDevice.NewFrame += VideoCaptureDevice_NewFrame;
            videoCaptureDevice.Start();

            Add_Log("Camera \"" + filterInfoCollection[cmbCameraS.SelectedIndex-1].Name+ "\" Connected!");
        }
        else
        {
            disconnectVideoStream(true);
        }
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        //Camera System
        cmbCameraS.Items.Add("None");
        filterInfoCollection = new FilterInfoCollection(FilterCategory.VideoInputDevice);
        foreach (FilterInfo filterInfo in filterInfoCollection)
        {
            cmbCameraS.Items.Add(filterInfo.Name);
        }
        cmbCameraS.SelectedIndex = 0;
        videoCaptureDevice = new VideoCaptureDevice();

        //Log
        shortLog.Parent = cameraDisplay;
        shortLog.BackColor = Color.Transparent;
        shortLog.Text = serialLog[0] + "\n" + serialLog[1] + "\n" + serialLog[2];

        //Loop
        Timer tmr = new Timer();
        tmr.Interval = 1000;
        tmr.Tick += Tmr_Tick;
        tmr.Start();

        //DisplayObjects
        MPpanel.Parent = cameraDisplay;
    }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        disconnectVideoStream(false);
    }

    private void SLbtn_Click(object sender, EventArgs e)
    {
        if (sld_form == null)
        {
            sld_form = new SerialLogDisplay(this);
        }
        sld_form.update_log(serialLog);
        sld_form.Show();
        sld_form.Activate();
    }

    //Created Methods
    //Serial Log
    private void Tmr_Tick(object sender, EventArgs e)
    {
        String lm = "";
        for (int i = 4; i >= 0; i--)
        {
            if (serialLog[i] != null)
            {
                lm += serialLog[i] + "\n";
            }
        }
        shortLog.Text = lm;
    }

    public void SerialLogDisplay_Close()
    {
        sld_form = null;
    }

    public void Add_Log(String text)
    {
        for (int i = serialLog.Length - 1; i > 0; i--)
        {
            serialLog[i] = serialLog[i - 1];
        }
        serialLog[0] = text;

        if(sld_form != null)
        {
            sld_form.update_log(serialLog);
        }
    }

    public void Serial_Command(String command)
    {
        Add_Log(command);
        //Send command to serial Port
    }

    //Camera Output
    private void VideoCaptureDevice_NewFrame(object sender, NewFrameEventArgs eventArgs)
    {
        try
        {
            if (cameraDisplay.Image != null)
            {
                cameraDisplay.Image.Dispose();
            }
            Bitmap bmp = (Bitmap)eventArgs.Frame.Clone();
            cameraDisplay.Image = bmp;
        }
        catch (System.InvalidOperationException ex)
        {
            Add_Log("Error: " + ex);
            disconnectVideoStream(true);
        }

        //GC.Collect();
    }

    private void disconnectVideoStream(bool showLog)
    {
        if (videoCaptureDevice.IsRunning == true)
        {
            if (showLog) { Add_Log("Disconnecting video stream..."); }
            videoCaptureDevice.SignalToStop();
            videoCaptureDevice.WaitForStop();
            videoCaptureDevice.Stop();
            cameraDisplay.Image = null;
        }

        if (showLog)
        {
            Add_Log("No camera connected!");
        }
        GC.Collect();
    }
}

修改后的Invoke代码(修复窗口操作异常但导致关闭崩溃)

private void VideoCaptureDevice_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
    Invoke(new Action(() => {
        try
        {
            if (cameraDisplay.Image != null)
            {
                cameraDisplay.Image.Dispose();
            }
            Bitmap bmp = (Bitmap)eventArgs.Frame.Clone();
            cameraDisplay.Image = bmp;
        }
        catch (System.InvalidOperationException ex)
        {
            Add_Log("Error: " + ex);
            disconnectVideoStream(true);
        }
    }));
    //GC.Collect();
}

解决方案

1. 解决多线程操作控件异常

AForge的NewFrame事件在非UI线程触发,直接操作PictureBox会引发跨线程访问异常。使用Invoke是正确方向,但需要先判断控件是否已被销毁,同时优化Bitmap的释放逻辑:

private void VideoCaptureDevice_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
    // 先检查控件状态,避免窗口关闭后触发无效操作
    if (!cameraDisplay.IsHandleCreated || cameraDisplay.Disposing || cameraDisplay.IsDisposed)
    {
        return;
    }

    // 使用BeginInvoke替代Invoke,避免阻塞摄像头采集线程
    BeginInvoke(new Action(() => {
        try
        {
            // 先保存旧图像,赋值新图像后再销毁旧图,避免控件正在使用旧图时被释放
            Bitmap oldBitmap = cameraDisplay.Image as Bitmap;
            Bitmap newBitmap = (Bitmap)eventArgs.Frame.Clone();
            cameraDisplay.Image = newBitmap;
            oldBitmap?.Dispose();
        }
        catch (System.InvalidOperationException ex)
        {
            Add_Log("Error: " + ex.Message);
            disconnectVideoStream(true);
        }
    }));
}

2. 修复程序关闭崩溃问题

在窗口关闭时,需要确保摄像头线程完全停止,且不再触发NewFrame事件,同时避免操作已销毁的控件:

  • 修改disconnectVideoStream方法,先移除事件绑定,再停止设备:
private void disconnectVideoStream(bool showLog)
{
    if (videoCaptureDevice != null && videoCaptureDevice.IsRunning)
    {
        // 先移除事件处理,防止停止过程中还触发NewFrame
        videoCaptureDevice.NewFrame -= VideoCaptureDevice_NewFrame;
        
        if (showLog) { Add_Log("Disconnecting video stream..."); }
        
        videoCaptureDevice.SignalToStop();
        // 设置超时时间,避免无限等待设备停止
        if (!videoCaptureDevice.WaitForStop(500))
        {
            videoCaptureDevice.Stop();
        }
    }

    // 安全释放PictureBox的图像资源
    if (cameraDisplay != null && !cameraDisplay.IsDisposed)
    {
        var img = cameraDisplay.Image;
        cameraDisplay.Image = null;
        img?.Dispose();
    }

    if (showLog)
    {
        Add_Log("No camera connected!");
    }
}
  • 在FormClosing事件中,确保所有未完成的UI操作执行完毕后再关闭窗口:
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    disconnectVideoStream(false);
    // 等待所有未完成的Invoke/BeginInvoke操作执行完毕
    Application.DoEvents();
}

3. 额外优化点

  • 避免频繁创建VideoCaptureDevice实例,可复用或确保旧实例被正确释放;
  • 不要在NewFrame事件中调用GC.Collect(),手动触发垃圾回收会影响性能,交给自动垃圾回收机制即可;
  • 日志操作Add_Log如果涉及UI控件更新,也需要判断是否跨线程,必要时使用Invoke确保线程安全。

内容的提问来源于stack exchange,提问作者Bruno Ferreira

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 13:50:22