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

C# PictureBox每秒多次更新画面时内存泄漏问题

问题根因

你观测到的内存周期性波动、超时参数和稳定段时长负相关的现象,是三个典型实现错误叠加导致的,和“位图释放速度跟不上帧率”的猜测部分吻合,但核心问题不在释放速度:

  • 跨线程违规操作UI控件导致资源回收逻辑失效:AForge的NewFrame事件运行在后台采集线程,并非WinForms UI线程。直接在非UI线程修改PictureBox.Image属性,会绕过控件内部绑定的GDI资源生命周期管理逻辑,旧Bitmap的句柄不会被即时标记为可回收,只能等GC执行二代回收时批量释放,这就是内存“上涨-稳定-再上涨”周期性波动的直接来源。
  • 锁对象选择错误导致锁竞争不符合预期:你直接拿PictureBox控件实例作为Monitor的锁对象存在严重问题——WinForms控件在执行重绘、属性更新等内部操作时,会自行锁定自身实例,你的采集线程和UI线程会频繁争抢同一个锁,1ms超时下大部分帧都会抢锁失败直接丢弃,调高超时后抢锁成功的帧数上升,自然更快触达GDI资源回收阈值,对应稳定段时长缩短。
  • 帧处理逻辑存在隐性资源积压风险:短超时丢帧的逻辑没有和UI线程的渲染节奏对齐,一旦出现UI卡顿,要么出现帧队列积压,要么出现克隆后的位图没被正确赋值、也没被显式释放的情况,进一步推高内存占用。
修复方案

按照以下逻辑重构帧处理代码,可以彻底解决内存持续上涨问题,同时自动适配UI渲染能力丢帧,不会出现帧积压:

  1. 定义独立的专用锁对象,禁止使用UI控件实例作为锁。
  2. 所有对PictureBox属性的读写操作,全部投递到UI线程执行,绝对不要在采集线程直接操作UI控件。
  3. 增加单帧缓存标记:任何时候只保留一帧待渲染的画面,只要缓存里有未渲染的帧,新到的帧直接克隆后释放,不进入渲染队列,从根源避免积压。
  4. 旧位图的释放操作放到UI线程执行,减少锁持有时间,避免和UI内部锁产生竞争。

修复后参考代码

首先定义类级别字段:

// 专用帧锁对象,禁止替换为控件实例
private readonly object _frameSyncLock = new object();
// 待渲染帧缓存
private Bitmap _waitRenderFrame = null;

重写NewFrame事件处理逻辑(运行在AForge采集线程):

private void Camera_NewFrame(object sender, NewFrameEventArgs e)
{
    // 必须先克隆当前帧,AForge会在事件执行结束后自动释放e.Frame
    Bitmap currentClonedFrame = (Bitmap)e.Frame.Clone();
    bool needNotifyUi = false;

    lock (_frameSyncLock)
    {
        // 存在未渲染的待处理帧,直接丢弃当前帧,释放资源后返回
        if (_waitRenderFrame != null)
        {
            currentClonedFrame.Dispose();
            return;
        }
        // 无待渲染帧,将当前帧写入缓存,标记需要通知UI更新
        _waitRenderFrame = currentClonedFrame;
        needNotifyUi = true;
    }

    if (needNotifyUi)
    {
        // 异步投递到UI线程渲染,不要用Invoke同步等待,避免阻塞采集线程
        _pictureBox.BeginInvoke((Action)(() =>
        {
            Bitmap oldBmp = null;
            lock (_frameSyncLock)
            {
                oldBmp = _pictureBox.Image as Bitmap;
                _pictureBox.Image = _waitRenderFrame;
                // 清空待渲染标记,允许下一帧进入缓存
                _waitRenderFrame = null;
            }
            // 锁外释放旧位图,减少锁占用时长
            oldBmp?.Dispose();
        }));
    }
}

补充注意事项

  • 摄像头停止采集时,需要在UI线程执行收尾:释放_pictureBox.Image、若_waitRenderFrame不为空也一并释放,避免退出时残留资源。
  • 其他业务逻辑如果要使用PictureBox显示的画面,必须自行Clone一份再使用,用完及时释放,不要直接持有PictureBox.Image的引用,避免资源冲突。
  • 修复后内存出现几十MB级别的周期性波动属于正常现象,是GC批量回收托管内存、GDI子系统批量回收图形资源的固有机制,只要内存没有持续线性上涨、进程GDI句柄数稳定在固定区间,就不存在泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 13:36:17