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

C# PictureBox抛出System.InvalidOperationException异常求助

问题描述

尝试每隔3秒从摄像头拍摄照片,运行代码时始终抛出异常:System.InvalidOperationException: "Object is currently in use elsewhere."。尝试用Mutex锁定资源仍报错,推测问题与Bitmap相关,但不确定具体涉及哪个Bitmap及问题位置。

相关代码:

//Here I declare global variables
FilterInfoCollection filterInfoCollection;
VideoCaptureDevice videoCaptureDevice;

Int16 INTERVAL;
string SAVESPACE;

private static Mutex mutex = new Mutex();

//Here I start the stream of my webcam from where I get my images
private void startNewCamera()
{
    videoCaptureDevice = new VideoCaptureDevice(filterInfoCollection[comboBox_Cameras.SelectedIndex].MonikerString);
    videoCaptureDevice.NewFrame += VideoCaptureDevice_NewFrame;
    videoCaptureDevice.Start();
}

//from my understanding this is where the live stream is being generated
//I also locked this one with a mutex
private void VideoCaptureDevice_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
    mutex.WaitOne();
    pictureBox_Preview.Image = (Bitmap)eventArgs.Frame.Clone();
    mutex.ReleaseMutex();
}


//Here I start with the backgroundworker from which I want to take a photo every 3 seconds
private void btn_Start_Click(object sender, EventArgs e)
{
    //start recording
    if (backgroundWorker_camera.IsBusy != true)
    {
        backgroundWorker_camera.RunWorkerAsync();
    }
}


//This is where the magic happens
//I lock the whole function but the error occurs nevertheless
private void backgroundWorker_camera_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
    while (!backgroundWorker_camera.CancellationPending)
    {
        System.Threading.Thread.Sleep(INTERVAL);
        mutex.WaitOne();
        
        //HERE THE ERROR ALWAYS OCCURS
        pictureBox_Capture.Image = pictureBox_Preview.Image;

        var bitmap = new Bitmap(pictureBox_Capture.Width, pictureBox_Capture.Height);
        pictureBox_Capture.Invoke(new Action(() =>
        {
            pictureBox_Capture.DrawToBitmap(bitmap, pictureBox_Capture.ClientRectangle);
        }));

        System.Drawing.Imaging.ImageFormat imageFormat = null;
        imageFormat = System.Drawing.Imaging.ImageFormat.Png;
        mutex.ReleaseMutex();                
    }    
}
报错原因分析
  1. Bitmap实例共享冲突:pictureBox_Capture.Image = pictureBox_Preview.Image直接让两个PictureBox控件引用同一个Bitmap对象。Bitmap本身不是线程安全的,当UI线程绘制其中一个控件时会锁定该实例,此时另一个控件或后台线程访问就会触发异常。
  2. Mutex使用范围不当:后台线程调用pictureBox_Capture.Invoke时,委托会在UI线程异步执行,而当前后台线程会立即释放Mutex。这导致UI线程执行DrawToBitmap时,NewFrame事件线程可能已经获取Mutex并修改Bitmap,引发资源占用冲突。
  3. 后台线程直接操作UI控件属性:pictureBox_Capture.Image是UI控件属性,直接在后台线程赋值违反WinForms的UI线程安全规则,可能引发不可预知的线程冲突。
解决方案

以下是修改后的代码,核心思路是避免Bitmap实例共享,用线程安全的方式传递最新帧,确保所有UI操作都在UI线程执行:

// 全局变量调整
FilterInfoCollection filterInfoCollection;
VideoCaptureDevice videoCaptureDevice;

Int16 INTERVAL;
string SAVESPACE;

// 用轻量的lock对象替代跨进程的Mutex
private static readonly object _frameLock = new object();
// 保存最新的摄像头帧,供拍照线程使用
private Bitmap _latestCameraFrame;

private void startNewCamera()
{
    videoCaptureDevice = new VideoCaptureDevice(filterInfoCollection[comboBox_Cameras.SelectedIndex].MonikerString);
    videoCaptureDevice.NewFrame += VideoCaptureDevice_NewFrame;
    videoCaptureDevice.Start();
}

private void VideoCaptureDevice_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
    lock (_frameLock)
    {
        // 释放旧的Bitmap,避免内存泄漏
        _latestCameraFrame?.Dispose();
        // 克隆帧保存到全局变量
        _latestCameraFrame = (Bitmap)eventArgs.Frame.Clone();
    }
    // 仅在UI线程更新预览控件
    pictureBox_Preview.Invoke(new Action(() =>
    {
        // 释放预览控件的旧Image
        pictureBox_Preview.Image?.Dispose();
        pictureBox_Preview.Image = (Bitmap)_latestCameraFrame.Clone();
    }));
}

private void btn_Start_Click(object sender, EventArgs e)
{
    if (!backgroundWorker_camera.IsBusy)
    {
        backgroundWorker_camera.RunWorkerAsync();
    }
}

private void backgroundWorker_camera_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
    while (!backgroundWorker_camera.CancellationPending)
    {
        System.Threading.Thread.Sleep(INTERVAL);
        
        Bitmap captureFrame = null;
        // 锁定获取最新帧,克隆后立即释放锁
        lock (_frameLock)
        {
            if (_latestCameraFrame != null)
            {
                captureFrame = (Bitmap)_latestCameraFrame.Clone();
            }
        }

        if (captureFrame == null) continue;

        // 在UI线程处理拍照和显示逻辑
        pictureBox_Capture.Invoke(new Action(() =>
        {
            // 释放旧的捕获图像
            pictureBox_Capture.Image?.Dispose();
            pictureBox_Capture.Image = captureFrame;

            // 执行截图保存(按需启用)
            var saveBitmap = new Bitmap(pictureBox_Capture.Width, pictureBox_Capture.Height);
            pictureBox_Capture.DrawToBitmap(saveBitmap, pictureBox_Capture.ClientRectangle);
            // saveBitmap.Save(SAVESPACE + $"_{DateTime.Now:yyyyMMddHHmmss}.png", System.Drawing.Imaging.ImageFormat.Png);
            saveBitmap.Dispose(); // 释放临时Bitmap
        }));
    }    
}

关键修改点:

  • 用lock替代Mutex:本场景是单进程内线程同步,lock比Mutex更轻量高效。
  • 新增_latestCameraFrame存储最新帧:避免直接共享PictureBox的Image实例,所有线程通过该变量获取帧且操作时加锁。
  • 所有UI控件操作通过Invoke在UI线程执行:遵守WinForms的UI线程安全规则,避免跨线程直接操作控件属性。
  • 及时释放Bitmap资源:每次替换Image时都Dispose旧实例,避免内存泄漏。
提问改进建议
  1. 补充使用的库信息:比如VideoCaptureDevice属于AForge.NET库,标注后能帮助回答者更快定位API特性和常见问题。
  2. 补充调试细节:比如是否尝试过打印线程ID确认执行顺序,是否排查过Bitmap的Dispose时机是否正确。
  3. 明确需求细节:比如拍照后是否需要保存到本地(当前代码有SAVESPACE变量但未使用),可让解决方案更贴合实际需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 02:25:40