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(); } }
报错原因分析
- Bitmap实例共享冲突:
pictureBox_Capture.Image = pictureBox_Preview.Image直接让两个PictureBox控件引用同一个Bitmap对象。Bitmap本身不是线程安全的,当UI线程绘制其中一个控件时会锁定该实例,此时另一个控件或后台线程访问就会触发异常。 - Mutex使用范围不当:后台线程调用
pictureBox_Capture.Invoke时,委托会在UI线程异步执行,而当前后台线程会立即释放Mutex。这导致UI线程执行DrawToBitmap时,NewFrame事件线程可能已经获取Mutex并修改Bitmap,引发资源占用冲突。 - 后台线程直接操作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旧实例,避免内存泄漏。
提问改进建议
- 补充使用的库信息:比如
VideoCaptureDevice属于AForge.NET库,标注后能帮助回答者更快定位API特性和常见问题。 - 补充调试细节:比如是否尝试过打印线程ID确认执行顺序,是否排查过Bitmap的Dispose时机是否正确。
- 明确需求细节:比如拍照后是否需要保存到本地(当前代码有
SAVESPACE变量但未使用),可让解决方案更贴合实际需求。
内容的提问来源于stack exchange,提问作者jfst
相关产品推荐
相关产品推荐

