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

C#图像处理代码出现"Object in use"错误的解决求助

解决C# WinForms图像处理"Object in use"错误及线程安全问题

问题根源

  1. 跨线程直接操作UI控件:WinForms控件并非线程安全,后台线程直接访问pictureBox1、flowLayoutPanel1及其子控件,会引发并发访问冲突,包括对象被占用的错误。
  2. Bitmap被UI控件占用时操作:直接给pictureBox1.Image赋值后立即用Graphics.FromImage操作该Bitmap,此时PictureBox可能正在使用这个Bitmap进行界面绘制,导致"Object in use"异常。

解决方案

核心思路是:后台线程只处理纯数据和图像绘制逻辑,所有UI控件的访问/更新都切换到UI线程执行;绘制用的Bitmap独立于UI控件,完成绘制后再同步到PictureBox。

步骤1:定义数据存储类

先封装绘制所需的所有数据,避免后台线程直接访问控件:

private class DrawItem
{
    public Bitmap Image { get; set; }
    public float Opacity { get; set; }
    public Rectangle DestRect { get; set; }
    public Rectangle SourceRect { get; set; }
}

步骤2:重构线程方法

修改piccc方法,确保线程安全:

private void piccc(object s)
{
    // 1. 从UI线程获取所有绘制所需的数据
    List<DrawItem> drawItems = null;
    flowLayoutPanel1.Invoke(new Action(() =>
    {
        drawItems = new List<DrawItem>();
        foreach (Control c in flowLayoutPanel1.Controls)
        {
            if (((Label)c.Controls[0]).Text == "   ")
            {
                PictureBox pb = (PictureBox)c.Controls[1];
                string[] ll3 = pb.Name.Split(',');
                
                // 复制原图片,避免原Bitmap被PictureBox占用
                Bitmap imgCopy = new Bitmap(pb.Image);
                
                drawItems.Add(new DrawItem
                {
                    Image = imgCopy,
                    Opacity = (float)int.Parse(ll3[6]) / 100,
                    DestRect = new Rectangle(int.Parse(ll3[0]), int.Parse(ll3[1]), int.Parse(ll3[11]), int.Parse(ll3[12])),
                    SourceRect = new Rectangle(int.Parse(ll3[2]), int.Parse(ll3[3]), int.Parse(ll3[4]), int.Parse(ll3[5]))
                });
            }
        }
    }));

    // 获取PictureBox的尺寸(UI线程执行)
    int pbWidth = 0, pbHeight = 0;
    pictureBox1.Invoke(new Action(() =>
    {
        pbWidth = pictureBox1.Width;
        pbHeight = pictureBox1.Height;
    }));

    // 2. 后台线程独立绘制图像
    using (Bitmap tempBitmap = new Bitmap(pbWidth, pbHeight))
    {
        using (Graphics gfx = Graphics.FromImage(tempBitmap))
        {
            ColorMatrix matrix = new ColorMatrix();
            ImageAttributes attributes = new ImageAttributes();
            
            foreach (var item in drawItems)
            {
                matrix.Matrix33 = item.Opacity;
                attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
                gfx.DrawImage(item.Image, item.DestRect, item.SourceRect.X, item.SourceRect.Y, item.SourceRect.Width, item.SourceRect.Height, GraphicsUnit.Pixel, attributes);
                
                // 释放临时复制的图片
                item.Image.Dispose();
            }
        }

        // 3. 回到UI线程更新PictureBox
        pictureBox1.Invoke(new Action(() =>
        {
            // 先释放旧图像,避免内存泄漏
            pictureBox1.Image?.Dispose();
            pictureBox1.Image = new Bitmap(tempBitmap);
        }));
    }
}

关键注意事项

  • 必须用Invoke跨线程访问控件:所有读取控件属性、遍历控件集合、更新控件的操作,都要通过Control.Invoke切换到UI线程执行,这是WinForms线程安全的强制要求。
  • 复制Bitmap避免占用冲突:直接使用PictureBox绑定的Bitmap会导致该对象被UI线程占用,后台线程无法操作,因此需要复制一份独立的Bitmap用于绘制。
  • 及时释放资源:使用using语句或手动Dispose临时Bitmap和旧的PictureBox Image,防止内存泄漏。
  • 优先使用Task.Run替代手动Thread:可以用Task.Run(() => piccc(null))替代手动创建Thread,代码更简洁且符合现代异步编程规范。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 22:53:14