在 dataset 中的 AfterScroll 中有利用多线程读取记录对应的图片
当用户快速拖动当前行,而造成发生大量 AfterScroll 事件时同时产生大量线程时,系统很容易报内存错误。想知道各位大侠是怎么处理类似的应用的!

解决方案 »

  1.   

    FREE  释放内存哦  
      

  2.   

    是在线程内发消息给主程序来处理同步问题的,问题在于很多线程没有返回的消息我通过一个计数来检查生成的线程和返回的线程,就是生成了线程就 +1 ,返回了线程就 -1 我现在用的 windows2003 系统上运行,出现的错误只是当那个计数到了 好几百的时候,会出现“系统无法创建更多线程”的错误。如果没有报这个错误,最后还是会减到 0  的。但在另外一个 XP 系统的笔记本上面,还没有到很多计数的时候就容易报内存错误了,都无法跟踪到的内存错误
      

  3.   

        我是在线程里面找到图片文件后,发消息给主进程,wParam 是一个字符串指针, lParams 是一个 List 的结构指针,包含了 图片 Graphic 的一个列表
        主程序受到消息,把图片取出来,然后把相关资源释放掉。
      

  4.   

    给我的感觉是,windows2k3 下面运行,应该是正常的反应,因为线程都返回来了而在 xp 下运行的,由于线程没有返回,可能会在那个计数是 十多个的时候就发生内存错误了
      

  5.   

    那你就不要在AfterScroll立刻建立新线程,等到用户停下来再建立新线程。
      

  6.   


    线程与主线程同步,这是一个同步,还有线程和线程之间的同步!打个比方说,线程A、B都要访问某一资源,A线程Free了某个资源,随后的B又去访问,于是就会发生错误,因为资源已经被A线程废除了!多线程的内存错误,多半是没有同步好,出现A线程已删除了某资源,B线程随后又去访问的错误。
      

  7.   


    这里就是一个Bug!因为有多个线程要对该变量进行操作!
      

  8.   

    to sy_100000:生成线程时,发送一个“线程开始了”的消息给主进程
    找到图片后,发送一个“图片找到了”的消息给主进程图片的处理,以及那个 计数 我都是在消息中操作的。
    难道主进程的消息响应事件中也要做同步处理?不应该吧
      

  9.   

    to lhylhy:你的这个建议挺好,虽然不是解决了线程错误的问题,但控制了发生错误的机会
      

  10.   

    最好是把基础的框架代码贴上来给大家研究研究,否则只是猜想!比如用消息和主线程同步,用SendMessage就可以,用PostMessage就完蛋!
      

  11.   

    嗯,但是用 SendMessage 的话,输入法切换时有可能会造成程序假死
      

  12.   

    设置一个全局变量比如: imageThreadBusy:boolean;
    每次调用辅助线程读取图片时先判断是否 imagethreadbusy=false 否则,忽略。
    同时需要做到辅助线程在每次读出图片后,先不显示,先判断当前记录的主键是否与辅助线程读取图片记录主键相同,如果相同表示就是本记录,显示图片,把imageThreadBusy设置成false;如果不相同,说明记录已经移动,就不要显示本图片了,而且辅助线程重新按照当前记录的主键读取图片。
      

  13.   

    感谢各位兄弟(也许还有姐妹:),总结一下:1. 多线程的资源释放是个关键,对于自己线程所涉及到的资源,一定要做到心中有数
    2. 系统总的可以使用的线程资源是有限的,对于没有必要的线程,尽量控制到最低,AfterScroll 是可以进行判断的,下面给出一个写出来的类
    3. 在线程和主进程之间使用消息来进行同步时避免同步问题一个比较好的方法。下面是刚写的一个用来检测 afterscroll 时间执行间隔的类
      TDtTimer = class(TObject)
      private
        fTick:integer;
        fTimer:TTimer;
        fDataSet:TDataSet;
        fOnAfterScroll:TDataSetNotifyEvent;
        fInterval:integer;
        procedure OnTimer(Sender:TObject);
      public
        constructor Create(DataSet:TDataSet; OnAfterScroll:TDataSetNotifyEvent; Interval:integer=200);
        destructor Destroy;override;
        function CheckTick:boolean;
        procedure ClearTick;
      end;{ TDtTimer }function TDtTimer.CheckTick:boolean;
    var
      it:integer;
    begin
      result := false;  
      it := GetTickCount;
      try
        if (it - fTick)<fInterval then
        begin
          fTimer.Enabled := true;
          exit;
        end;
      finally
        fTick := it;
      end;  result := true;
    end;procedure TDtTimer.ClearTick;
    begin
      fTick := 0;
      fTimer.Enabled := false;
    end;constructor TDtTimer.Create(DataSet: TDataSet; OnAfterScroll:TDataSetNotifyEvent; Interval:integer);
    begin
      fDataSet := DataSet;
      fOnAfterScroll := OnAfterScroll;
      fInterval := Interval;  fTimer := TTimer.Create(nil);
      fTimer.Interval := fInterval;
      fTimer.Enabled := false;
      fTimer.OnTimer := OnTimer;
    end;destructor TDtTimer.Destroy;
    begin
      fTimer.Enabled := false;
      fTimer.Free;
      fDataSet := nil;
      inherited;
    end;procedure TDtTimer.OnTimer(Sender: TObject);
    begin        
      if not Assigned(fDataSet) then
        exit;
      
      if fTimer.Tag=0 then
      begin
        fTimer.Tag := 1;
        exit;
      end;
      fTimer.Enabled := false;
      fTimer.Tag := 0;      if Assigned(fOnAfterScroll) then
        fOnAfterScroll(fDataSet);
    end;
    加入 Timer 的目的是判断停止滚动后运行一遍 AfterScroll 
      

  14.   

    to tieshui_zjf:因为可能存在多个正在执行的线程,使用一个全局的判断标志会不会不太妥当?是否可以考虑用一个计数来防止执行的线程过多?按顺序创建的线程并不一定会按顺序返回完成消息,你的判断是否是当前记录的提醒是必须的,否则得到的结果可能不是所希望的结果
      

  15.   


    感觉是应该这样处理
    已经在读取图片i时,应该忽略继续滚动导致的新的下1个、下2个的图片读取的企图,
    等待读取完图片i后,判断最新的企图是哪一个图片,就去读那个图片,而跳过第i+1/i+2/...个图片标志可以放在Tform1里,地址传给读取图片的线程,由它最后复位此标志
    scroll事件判断此标志,如果为真,说明已经在读取图片,就不再运行新的线程而直接略过,
    如果为假,说明最近的图片已经读取完成,才把标志置为真,并去运行新的线程
      

  16.   

    不行了,之前那个处理方法只能减少了多线程的触发,但在另外一个窗口,不是 dbgrid 的窗口,同时触发大概35个线程,报错了
    还是只是在 Xp 上的系统出错,2003的系统就不会。下面我贴出代码,让大家帮我查一下,我实在搞的郁闷了
      

  17.   

    说明:
    1. 我是在动态链接库里做的图片文件检索功能
    2. 在动态链接库里使用多线程查找,然后发送消息给主程序
    3. 主程序不负责收到的 PicList(图片Graphic列表) 对象的释放,还是在 dll 中进行释放
      

  18.   


    // 第一个调用函数 
    // Frame 是我做的一个主程序接口(纯虚类)
    // gFrame, gTemPictures 是 dll 中的全局变量
    procedure GetTemPictures(Frame:TSeaFrameInt; Handle:THandle; TemCode:string; bRefresh:boolean; FileFilters:string);stdcall;
    begin
      if gFrame=nil then gFrame := Frame;  if gTemPictures=nil then gTemPictures := TTemPictures.Create(gFrame,'');  gTemPictures.GetTemPicture(Handle,TemCode,bRefresh,FileFilters);
    end;// 类 TTemPictures 的取图片函数
    procedure TTemPictures.GetTemPicture(Handle:THandle; TemCode: string; bRefresh: boolean; FileFilters:string);
    var
      lt:TPicList;
      th:TThread;
    begin
      if Handle<=0 then
        exit;
      if TemCode='' then
        exit;
                         
      // 发送消息给主程序:我开始取图片了
      SendMessage(Handle,UM_TemPicGeting,Longint(PChar(TemCode)),0);
      
      if var_bool(fFrame.DBParams['bAsynGetPicture']) then
      begin
        TPicThread.Create(Handle,TemCode,bRefresh,FileFilters); // 多线程处理方式
      end
      else
      begin
        lt := GetPicList(TemCode,bRefresh,FileFilters);
        SendMessage(Handle,UM_TemPicGeted,longint(PChar(TemCode)),longint(lt)); // 非多线程处理方式
      end;
    end;
      

  19.   


    // 取图片的多线程类
    TPicThread=class(TThread)
      private
        fHandle:THandle;
        fTemCode:string;
        fRefresh:boolean;
        fFileFilters:string;   
        procedure ProcessMessage;
      public
        constructor Create(Handle:THandle; TemCode:string; bRefresh:boolean=false; FileFilters:string=''; bSuspended:boolean=false);
        destructor Destroy;override;
        procedure Execute;override;
      end;{ TPicThread }constructor TPicThread.Create(Handle: THandle; TemCode: string;
      bRefresh: boolean; FileFilters: string; bSuspended: boolean);
    begin
      fHandle := Handle;
      fTemCode := TemCode;
      fRefresh := bRefresh;
      fFileFilters := FileFilters;
      
      FreeOnTerminate := true;
      
      inherited Create(bSuspended);
    end;destructor TPicThread.Destroy;
    begin  inherited;
    end;procedure TPicThread.Execute;
    var
      lt :TPicList;
    begin
      inherited;  if not Assigned(gTemPictures) then
        exit;  lt := GetPicList(fTemcode,fRefresh,fFileFilters);
      SendMessage(fHandle,UM_TemPicGeted,longint(PChar(fTemCode)),longint(lt));
    end;procedure TPicThread.ProcessMessage;
    beginend;
      

  20.   

    // 检索图片文件并得到图片列表的一个全局函数
    // 疑问:1. 在线程中执行全局函数有没有问题? 
    //      2. 同步的问题,这个全局函数中只有对全局对象的读操作,没有写操作,是否也要同步?function GetPicList(TemCode:string; bRefresh:boolean=false; FileFilters:string=''):TPicList;
    var
      i,j:integer;
      fileName,OldPicFiles:string;
      pic:TPicture;
      apx:TSeaParams;  
      fplt,lt:TStringList;
      ltPicFiles:TStringList;
      ic:integer;
    begin
      result := nil;  if not Assigned(gTemPictures) then
        exit;
      if not gTemPictures.fPathDBExists then
        exit;  TemCode := trim(TemCode);
      bRefresh := bRefresh;
      FileFilters := trim(FileFilters);
      
      if TemCode='' then
        exit;
      if FileFilters='' then
        FileFilters := TemCode;  // 从内存中查找款号记录
      apx := TSeaParams.Create;
      pic := TPicture.Create;
      ltPicFiles := TStringList.Create;
      try
        result := TPicList.Create;
        if not bRefresh then
        begin
          //ic := GetTickCount;
          gFrame.GetQueryReturnSmart('select convert(varchar(1000),PicFiles) as xx from TempletPictures where TemCode='''+ SwapStr(TemCode) +'''',apx);
          // 上面这个函数是利用主框架接口执行查询语句并返回的函数
           // 它会使用新的数据库连接,创建新的 dataset 来得到结果集的
           // 这里面应该不会存在需要要同步的问题,因为我把它注释掉一样报内存错误
          StrToList(SwapStr(apx.AsString('xx'),'\','\\'),';',ltPicFiles);
        end;
        OldPicFiles := apx.AsString('xx');    // 取得所有可能且存在的文件名称
        if ltPicFiles.Count=0 then
        begin
          // 如果之前没有图片,则一定刷新
          bRefresh := true;
          lt := TStringList.Create;
          fplt := TStringList.Create;
          try
            fplt.Assign(gTemPictures.fPicPaths);
            StrToList(FileFilters,',',lt);
            for i:=0 to lt.Count-1 do
            begin
              for j:=0 to fplt.Count-1 do
              begin
                // 严格匹配款号
                  SearchFiles(fplt[j],faAnyFile,lt[i] +'.jpg',OnSearchFiles,ltPicFiles);
                // 匹配 款号+'.*.jpg';
                SearchFiles(fplt[j],faAnyFile,lt[i] +'.*.jpg',OnSearchFiles,ltPicFiles);
                // 匹配 款号+'(*).jpg';
                SearchFiles(fplt[j],faAnyFile,lt[i] +'(*)*.jpg',OnSearchFiles,ltPicFiles);
                // 上面的函数是查找文件的过程,找到的结果保存到 ltPicFiles:TStringList 中
              end;
            end;
          finally
            lt.Free;
            fplt.Free;
          end;
        end;    // 取得图片
        for i:=0 to ltPicFiles.Count-1 do
        begin
          fileName := gTemPictures.fPathDB + ltPicFiles[i];
          try
            pic.LoadFromFile(fileName)
          except
            fileName := '';
          end;
            
          if (fileName<>'') and (not pic.Graphic.Empty) then
            result.AddItem(pic.Graphic,ltPicFiles[i]);
        end;                       // 如果刷新,保存信息进数据库
        if bRefresh then TSPFThread.Create(TemCode,ListToStr(ltPicFiles,';'));
        // 这个语句是保存结果到数据库,下次就可以不用再检索文件
         // 应该也不是它的问题 
      finally
        ltPicFiles.Free;
        pic.Free;
        apx.Free;
      end;
    end;
      

  21.   


    //下面是窗口中的消息响应函数
    // 响应开始检索消息
    procedure TTempletAlbumFrm.OnTemPicGeting(var msg: TMessage);
    var
      TemCode:string;
      pv:TPicView;
    begin          
      if msg.WParam=0 then
        exit;
      TemCode := PChar(msg.WParam);  pv := PicViewBox1.GetPicViewWithValue(TemCode);  if pv<>nil then
      begin
        pv.NoPicCaption := '正在检索...';
        pv.ClearGraphis;
      end;
    end;
    // 响应检索完成消息
    procedure TTempletAlbumFrm.OnTemPicGeted(var msg: TMessage);
    var
      TemCode:string;
      pv:TPicView;
    begin          
      if msg.WParam=0 then
        exit;
      TemCode := PChar(msg.WParam);  pv := PicViewBox1.GetPicViewWithValue(TemCode);  SetPicView(pv,TemCode,TPicList(msg.LParam));
      // 再次调用动态链接库的函数处理
      // 因为 dll 中生成的对象只能在 dll 中释放
    end;
      

  22.   


    // 这个函数用来设置对应的控件,并释放 PicList
    // TPicView 是一个用来显示多个图片的控件
    procedure TTemPictures.SetPicView(PicView: TPicView; TemCode: string; PicList: TPicList; bSet:boolean; PicName:string);
    var
      i,idx:integer;
    begin
      try
        if PicView=nil then
          exit;
        if (picList=nil) or (PicList.Count=0) then
        begin
          PicView.NoPicCaption := '无图片';
          PicView.ClearGraphis;
          exit;
        end;    if not bSet then
          exit;    PicView.BeginUpdate;
        try
          PicView.NoPicCaption := '无图片';
          PicView.ClearGraphis;
          for i:=0 to picList.Count-1 do
            PicView.AddGraphic(PicList.GetItem(i)^.Graphic,PicList.GetItem(i)^.Value,ExtractFileName(PicList.GetItem(i)^.Value));
          idx := 0;
          if PicName<>'' then
          begin
            idx := PicList.IndexOfValue(PicName);
            if idx<0 then idx := 0;
          end;
          picView.PicIndex := idx;
        finally
          PicView.EndUpdate;
        end;
      finally
        if PicList<>nil then PicList.Free;
      end;
    end;
      

  23.   

    如上面某位所说,只响应一部分图片请求就可以了。线程在收到请求的时候记下请求的目标和时间,然后在Execute里头检查这个值在一个短的时间内是否有变化,如果没有变化则执行请求并随后返回结果。