感谢各位兄弟(也许还有姐妹:),总结一下: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
to tieshui_zjf:因为可能存在多个正在执行的线程,使用一个全局的判断标志会不会不太妥当?是否可以考虑用一个计数来防止执行的线程过多?按顺序创建的线程并不一定会按顺序返回完成消息,你的判断是否是当前记录的提醒是必须的,否则得到的结果可能不是所希望的结果
// 第一个调用函数 // 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;
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;
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;
// 检索图片文件并得到图片列表的一个全局函数 // 疑问: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;
//下面是窗口中的消息响应函数 // 响应开始检索消息 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;
// 这个函数用来设置对应的控件,并释放 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;
主程序受到消息,把图片取出来,然后把相关资源释放掉。
线程与主线程同步,这是一个同步,还有线程和线程之间的同步!打个比方说,线程A、B都要访问某一资源,A线程Free了某个资源,随后的B又去访问,于是就会发生错误,因为资源已经被A线程废除了!多线程的内存错误,多半是没有同步好,出现A线程已删除了某资源,B线程随后又去访问的错误。
这里就是一个Bug!因为有多个线程要对该变量进行操作!
找到图片后,发送一个“图片找到了”的消息给主进程图片的处理,以及那个 计数 我都是在消息中操作的。
难道主进程的消息响应事件中也要做同步处理?不应该吧
每次调用辅助线程读取图片时先判断是否 imagethreadbusy=false 否则,忽略。
同时需要做到辅助线程在每次读出图片后,先不显示,先判断当前记录的主键是否与辅助线程读取图片记录主键相同,如果相同表示就是本记录,显示图片,把imageThreadBusy设置成false;如果不相同,说明记录已经移动,就不要显示本图片了,而且辅助线程重新按照当前记录的主键读取图片。
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
感觉是应该这样处理
已经在读取图片i时,应该忽略继续滚动导致的新的下1个、下2个的图片读取的企图,
等待读取完图片i后,判断最新的企图是哪一个图片,就去读那个图片,而跳过第i+1/i+2/...个图片标志可以放在Tform1里,地址传给读取图片的线程,由它最后复位此标志
scroll事件判断此标志,如果为真,说明已经在读取图片,就不再运行新的线程而直接略过,
如果为假,说明最近的图片已经读取完成,才把标志置为真,并去运行新的线程
还是只是在 Xp 上的系统出错,2003的系统就不会。下面我贴出代码,让大家帮我查一下,我实在搞的郁闷了
1. 我是在动态链接库里做的图片文件检索功能
2. 在动态链接库里使用多线程查找,然后发送消息给主程序
3. 主程序不负责收到的 PicList(图片Graphic列表) 对象的释放,还是在 dll 中进行释放
// 第一个调用函数
// 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;
// 取图片的多线程类
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;
// 疑问: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;
//下面是窗口中的消息响应函数
// 响应开始检索消息
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;
// 这个函数用来设置对应的控件,并释放 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;