getstr88 发表于 2022-6-25 22:27

C# 中 Iamge 与PictureBox 释放资源相关的一个问题

本帖最后由 getstr88 于 2022-6-25 22:29 编辑

下面代码,form载入时,load一张图片,然后让picbox1和2都显示它,点击button1,我以为的应该是这样的,因为还有picbox2引用这个image,所以不会disposed,等于不执行(而表现上,确实picbox1仍旧显示这个图片)
然后点击button2,是将image赋值给picbox3,我本以为这应该是没有问题的。因为我觉得反正image并没有被释放。但实际却报错了。报 System.ArgumentException:“Parameter is not valid.”

这我就有点不理解了。我打断点跟踪,发现整个过程,image指向内存地址也没有变动(在vs的即时视图中,用&_Image不断观察变化)

那出现这个的原因是什么呢? (我这个例子只是为了搞清楚原理,故意造的一个例子。请对C#内存机制熟悉的大佬指点一二。如果只是和我一样,一直用其他编程语言,只不过因为工作需要,临时抱佛脚突击学一下C#,只会点皮毛的朋友,就不要像我这样瞎猜了)

public partial class Form1 : Form

    {
      public Form1()
      {
            InitializeComponent();
      }

Image _Image;

      private void Form1_Load(object sender, EventArgs e)
      {
            _Image = LoadImageFile(@"d:\1.tif");
            pictureBox1.Image = _Image;
            pictureBox2.Image = _Image;
      }
public static Image LoadImageFile(string filePath)
      {
            FileStream fileStream = new FileStream(filePath, FileMode.Open);
            MemoryStream memoryStream = new MemoryStream();
            fileStream.CopyTo(memoryStream);
            Image image = Image.FromStream(memoryStream);

            fileStream.Close();
            return image;
      }
private void button1_Click(object sender, EventArgs e)
      {
            pictureBox1.Image.Dispose();
      }

      private void button2_Click(object sender, EventArgs e)
      {
            pictureBox3.Image = _Image;
      }
}

cdj68765 发表于 2022-6-25 22:54

因为对于C#来说
pictureBox1.Image = _Image;
pictureBox2.Image = _Image;
这两句话里面,相当于传的都是_Image指针,在 pictureBox1和 pictureBox2里面的Image都是同一个实例,你对 pictureBox1.Image进行释放,就相当于对_Image进行释放
那么再把释放过以后的图片传给pictureBox3就会报这个错
简单来说,C#里面的等于号赋值传的class类,基本上传的都是指针,指向的是同一块数据空间,只有对基本数据结构或者结构体赋值,传的才是数据的拷贝

unmask 发表于 2022-6-26 09:05

你已经显式的dispose了,那么gc已经开始接收你的dispose请求,不管你的对象是否被引用,gc都会按照它的方式回收这个对象的资源。所以,虽然指针是同一个,但是gc可能已经回收该数据,所以会报错。
以上只是我的自己的理解,可能有错误。

getstr88 发表于 2022-6-26 09:21

cdj68765 发表于 2022-6-25 22:54
因为对于C#来说
pictureBox1.Image = _Image;
pictureBox2.Image = _Image;


兄弟意思是,我picbox1.dispose,虽然picbox2也引用了image,但仍旧会释放?

确定是这样么?dispose是强制释放,不管是否还有其他引用? 但如果如此的话,为什么picbox1 dispose后,picbox1仍旧能显示图片,不是释放了么

getstr88 发表于 2022-6-26 09:35

unmask 发表于 2022-6-26 09:05
你已经显式的dispose了,那么gc已经开始接收你的dispose请求,不管你的对象是否被引用,gc都会按照它的方式 ...

我感觉picbox1.image.dispose并没有作用。即便我从下面加一行GC.Collect()

表现确实picbox1的图片仍旧显示,假如能被销毁,不应该不显示甚至报错了么?

另外,刚看了下C# gc,我看到有2个很重要结论:

1、没有办法手工强制释放托管堆的,至多调起GC.Collect让 C#自动回收垃圾资源
2、垃圾资源是没有引用的资源。上面我的代码,icbox2引用image,自然不属于垃圾资源。应该不允许被GC清理

unmask 发表于 2022-6-26 09:57

有dispose接口的是非托管的吧,托管对象才是你说的有引用,不会引起gc释放的。
pic框中显示的图片是UI缓存的,它只是在load时会读取对象,之后没有刷新/再加载指令前,即使对象销毁了,也不会影响UI显示的

getstr88 发表于 2022-6-26 10:15

unmask 发表于 2022-6-26 09:57
有dispose接口的是非托管的吧,托管对象才是你说的有引用,不会引起gc释放的。
pic框中显示的图片是UI缓存 ...
????

怎么是非托管资源呢?我那个loadImage,从文件流读取文件,然后放到内存流。文件流就关了。

image是受CLR管理的吧?

disllusion 发表于 2022-6-26 13:45

我也新手不太懂,不过C#尽量不手动回收好点,你把流文件的对象写在using(参数)中,至于图片对象的回收你交给自动回收机制好一点,不用或者不显示,你直接引用走或使用其他方式我觉得好一点

wxk0248 发表于 2022-6-26 14:55

楼主这种寻根求低的精神是值得肯定的,下面说说我的理解:一般来说我们识别托管对象和非托管对象就是看类有没有实现IDisposable接口,也就是有没有Dispose()方法,有Dispose()方法我们一般就会把它当做一个非托管对象对待。严格来说MemoryStream是一个非托管对象,因为他没有要释放的非托管资源,但是它又有Dispose()方法(是由MemoryStream从Stream类继承过来的),但是并不意味着MemoryStream执行Dispose()方法没有效果,它会直接把MemoryStream标记为不可用状态,也就是说Dispose以后就不允许你再对这个流进行读写了,具体什么时间对象进行回收那是由GC算法自行决定的,这就是你看到的&_Image指向的地址没有变化的原因,没有变化但是流被标记为不可读写了,再去访问肯定就会报错了。至于你说的picbox1 dispose后,仍旧能显示图片这个应该是picbox1控件本身的缓存机制,在Dispose以后可以通过访问pictureBox1.Image的属性来验证,比如pictureBox1.Image.Size,应该同样会报参数无效

getstr88 发表于 2022-6-26 16:07

wxk0248 发表于 2022-6-26 14:55
楼主这种寻根求低的精神是值得肯定的,下面说说我的理解:一般来说我们识别托管对象和非托管对象就是看类有 ...

非常感谢,明白了。不过今天评分机会用光了,明日再给兄弟分

另外还有个问题。调用GC.Collect(),会强制进行一次彻底的垃圾回收,还是如果C#发现内存足够,压根也不做GC呢? 因为我disposed后,强制掉了GC.Collect,发现&Image仍旧没变化,不知为啥。
页: [1] 2
查看完整版本: C# 中 Iamge 与PictureBox 释放资源相关的一个问题