当前位置:编程学习 > C#/ASP.NET >>

(高人请进)一个关于子线程无法释放PictureBox资源的怪题?

    请具备一定水平的高手解题!感激!
    本人做了一个弹窗监控程序:要实现对所辖20~30个部门的上报数据进行实时监视(每次监视的部门数不定),并把查询到的最新结果写到主窗口上,由于每次查询时间有点长,几十秒左右,所以设计了一个进度窗口提示用户查询进行到了哪一步,查询结束后,自动关闭这个进度窗口。看似简单的设计却出现了内存溢出的问题。具体设计和实现如下:
1、程序主要由一个主窗口、Timer控件和一个临时显示查询进度窗口组成。
2、添加了3个很小资源图片:1个会动的gif图片(Loading)、1个不会动的Ok字符图片、和1个不会动Wrong的字符图片
3、在一个进度窗体的设计里,预先添加了3个picbox控件显示上一步资源文件里的图片,名称为:picLoading、picOk、picWrong,还增加了一个TableLayOut控件,进度窗口设计成无边框(没有最大、最小化和关闭按钮的那一行标题行、只有窗体)
4、在主窗口添加一个Timer控件(Interval设为1000毫秒),根据需要,每分钟会定时建立20~30个线程对我要监控的数据进行数据库查询,查询期间会new一个进度窗口并弹出一个进度窗口,里面有一个空的TableLayOut控件,在进度窗体的InitializeComponent()后面,手动增加了和线程数量相同的20~30行控件,每行控件由1个PictureBox和1个Label控件组成,都分别加入到了TableLayOut控件的第1列和第2列,在进度窗口new的时候,new一个新的picX数组和labX数组,数组长度和查询线程数量相当。且将picX数组里的Image设置和picLoading相同,即picX[i].Image=picLoading.Image,在将LabX[i]字符初始为:“Id=某线程号,正在查询的”。
5、查询过程中,每个查询线程会不定时的调用进度窗口的ChangePic_And_Label()这个函数,向进度窗口发送改变哪一行picX里的图片和labX里的文字,参数依次为:当前线程的id号(和TabLayOut里的行一一对应)、需要改变的图片字符代号、 需要label显示的查询进度提示文字。
即: public void ChangePic_And_Label(int pId, string whichPic , string info)
    {
        lock(某个主窗体和进度窗体能访问的唯一对象) //防止多个线程同时向预设的picOk和picWrong读取image
        {
          select(whichPic)
          {
            case "ok":
                 picX[pId].image=picOk.image; //改变loading图片为ok图片
                 labX[pId].text=info;  //改变此行label文字为:“查询结束!”
                 break;
            case "notok":
                 picX[pId].image=picWrong.image; //改变loading图片为失败图片
                   labX[pId].text=info;  //改变此行label文字为:“查询失败!”
                 break;
          }
        }
    }
        
    
6、在主窗口进行查询时,先把进度窗口new出来,然后查询线程里通过委托调用调用进度窗口的ChangePic_And_Label函数来设置进度,如下:

    建一个查询类: 
Class QueryC
{
  private int _createThCounts; //本次查询需要建立多少个线程
 
  FormProgress frmPg; //保存此次查询用到的进度窗口 

  void QueryC(int mCreateThCounts) //构造函数,mCreateThCounts不会传值为零或负数
  {
    _createThCounts=mCreateThCounts;
  }

  public void ReadyQuery() //初始化查询线程,准备开始查询
  {
    frmPg=new FormProgress(); //new一个进度窗口后显示
     frmPg.Show();
    for(int i=0;i<=createThCounts;i++)
    {
     Thread th=new Thread(Do_Query);
     th.Name=i.ToStrign();   //用于表示线程对应于进度窗口的第几行
      th.IsBakGround=true;
     th.Start();
    }
  }
  
  //新建改变进度窗口对应行的pic和label的委托
  delegate DLG_ChangePic_And_Label(int pId, string whichPic , string info);

  //用于关闭进度窗口的委托
  delegate DLG_CloseProgFrom();
  
  private void Do_Query()
  {
    try
    {
     执行一些数据库查询的代码,略……;
      //调用委托在进度窗口里显示查询结果
      if(查询失败) this.Invoke(new DLG_ChangePic_And_Label(frmProg.DLG_ChangePic_And_Label,"当前线程Name转为int","设置该行图片为Wrong","查询失败,已退出!");
     
     if(查询成功) this.Invoke(new DLG_ChangePic_And_Label(frmProg.DLG_ChangePic_And_Label,"当前线程Name转为int","设置该行图片为ok","查询顺利,已完成!");

     }
    catch 
    {
     //调用委托在进度窗口里显示查询结果
      this.Invoke(new DLG_ChangePic_And_Label(frmProg.DLG_ChangePic_And_Label,"当前线程Name转为int","设置该行图片为Wrong","查询异常!");

    }
    finally
    {
      _createThCounts线程数量做原子减法;
      if(_createThCounts==0) //若所有线程结束
       {
         //调用委托关闭进度窗口
          this.Invoke(new DLG_CloseProgFrom(CloseProgFrom);
      }
    }
  }
}

7、调用方式: 在Timer的Tick函数里,写入如下:
if (DateTime.Now.Second == 10)
{
  QueryC qc=new QueryC(此次要创建的线程数量);
  qc.ReadyQuery();
}


8、出现的问题:运行一段时间后、程序遇到了内存溢出的错误!一般为几个小时后,而且打开windows任务管理器可以开到内存在不断一点一点增加,句柄数600~900间来回震荡。

9、在检测很久后发现,只要把进度窗口里改变pic控件Image的语句picX[pId].image=picOk.image;都注释掉,只留能改变label的那些语句,然后程序占用内存就一致维持在40M左右,这是为什么啊!不解,后面怀疑是不是改变原有的pic控件的image后就不会释放内存,于是把picX[pId].image=picOk.image;改为picX[pId].visible=false;让其隐藏,但还又会出现内存几个小时后溢出的错误,为什么只要在委托调用里改变picX[pId]的任何属性,都会在运行一段时间后出现内存溢出啊!如果说进度窗口关闭时不会释放内存,那么预设的那些20~30个label为什么又能呢?为什么用代码在进度窗口在初始化时自动建立的那20~30个picbox在不改变任何属性的情况下又可以被释放呢?后来直接改为不用委托来调用进度窗口里的函数,改为直接调用,可问题依旧!在进度窗口的dispose函数里加上了picX[pId]及TableLayOut的dispose,也不行!甚至在new进度窗口前,把QueryC类里frmPg对象dispose掉后再调用frmPg=new FormProgress()也不行!

还采用了再进度窗口类里设置一个改变pic的事件,在Query类里动态添加事件绑定,事件函数处理无论写在进度窗体里还是在主窗体里都出现上述问题!每次查询结束时还调用了-=来取消事件的绑定,也不行!

请高手们指点方向,自己不解啊! 多线程 PictureBox 释放 内存 资源 --------------------编程问答-------------------- 你自己手动创建线程当然会占用很多资源了,你可以尝试使用基于任务的异步编程模式来实现,针对你说的内存泄露的问题,你图片资源和线程都需要内存的,当你分配过多线程和图片而不及时释放的时候肯定会出现内存泄露的.你释放的应该是图片资源,对于线程的释放,当你线程运行完事情只要要确保它能够释放,你这样的实现方式控制起来太难,还是建议你学习基于任务的异步编程或基于事件的异步编程来重构的你程序,及时你现在的问题解决了可能后期还会出现内存的相关问题,因为你自己手动创建线程,然后再用代码来控制线程的释放,这样增加了实现的复杂度,.NET 中本身就线程池的概念的,你完全可以利用线程池来管理线程,这样就减少了你手动开启线程的代码,关于异步编程,你可以参考我的博客:
基于事件的异步编程模式
基于任务的异步模式
即将会发布一篇深入理解线程和线程池的文章出来,也希望你关注,同时你可以看看我的这篇文章来好好理解下线程的概念:
线程基础 --------------------编程问答-------------------- 其实你只要准备一些固定的图片切换就可以了。没有必要每次都产生。 --------------------编程问答-------------------- 感谢lizhi3186575 的提醒,但我用的是.net2.0的方式开发,没有用到4.0及以上的环境。还感谢
苏小喵,我已经把pic要用到的3个状态的图片:即 Loading(gif动态图)、Ok和Wrong都在编译前放到了Resoures资源里,而且还在进度窗口里建立了3个隐藏的picbox,分别显示这上述的3和图片,这3个图片资源及picbox都是固定的,只是那些实时显示线程查询状态的picbox是根据线程数量动态生成的,而且默认visible为true,默认显示的image=gif动画(loading的那张),然后随查询的过程,image依次被赋值为3个picbox里的其中一个。之所以每次都动态生成这些lable和picbox,是因为,每次查询的线程数量不一定都是20或30,而是随机的,所以采用了动态建立和线程数量相匹配的线程来完成各自的查询,并在查询时让这些线程及时向进度窗口里自己对应的那个picbox和label传递更改命令。

    现在的问题就是:主窗口每次查询时,new一个进度窗口后show出来,若此时我只改变进度窗口里那些动态生成20~30个的Label控件的text,等到所有线程查询结束后关闭进度窗口,经过测试后,无论运行几天都不会出现内存溢出(而且每一个label控件的的前面也动态的创建了一个显示loading动画的gif图片picbox控件)。

    但是如果我用同样的方法,不仅更改了Label控件的text,也更改了位于它们前面的每一个picbox的image后,那么程序在运行几个小时后就会报内存溢出,这是为什么呢?难道我只能改进度窗口里的Label,而不能改picbox吗?    请高人指导指导,自己真的不知道是什么原因啊!为什么改label就不会溢出,该picbox就会啊,甚至我只改动picbox的visible=false也会出现溢出,难道picbox有什么bug吗?在线程里改动picbox的任何一项值都会导致窗口关闭时释放不了内存吗? 
补充:.NET技术 ,  C#
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,