【发布时间】:2013-10-27 20:19:30
【问题描述】:
我一直在编写单元来搜索以指定扩展名结尾的文件,并且能够跳过对指定目录的搜索。此数据分别包含在 FExtensions 和 FIgnorePaths TStringList 对象中。
但是,大约每 10 次运行中就有 1 次出现线程崩溃,但出现以下异常:
经过一番调试,我在搜索线程中将这一行隔离为崩溃原因:
if FExtensions.IndexOf(ExtractFileExt(search_rec.Name)) <> -1 then
在致电IndexOf() 之前,我曾尝试进行Assigned(FExtensions) 检查,但这并没有消除崩溃。如果我评论这一行,线程压力测试工作正常(以 100 毫秒的间隔创建/销毁它)。我知道TStringList 不是线程安全的,但我不会在线程范围之外的任何地方访问FExtensions 或任何其他TStringList,因此并发访问不应该是崩溃的原因。
这里是文件搜索线程单元:
unit uFileSearchThread;
interface
uses
Winapi.Windows, System.Classes, System.Generics.Collections;
type
TFileSearchThread = class(TThread)
private
FExternalMessageHandler: HWND;
FMsg_FSTDone : Cardinal;
FPath : String;
FIgnorePaths : TStringList;
FExtensions : TStringList;
FFiles : TStringList;
function IsIgnoreDir(const ADir: String; out AKeepIgnoreCheck: Boolean): Boolean;
protected
procedure Execute; override;
public
constructor Create(const APath: String; const AIgnorePaths: TStringList; const AAllowedExtensions: TStringList; const AExternalMessageHandler: HWND; const AMsg_FSTDone: Cardinal);
destructor Destroy; override;
property Path : String read FPath;
property Files: TStringList read FFiles;
end;
TFileSearchThreads = TObjectList<TFileSearchThread>;
implementation
uses
System.SysUtils, System.StrUtils;
constructor TFileSearchThread.Create(const APath: String; const AIgnorePaths: TStringList; const AAllowedExtensions: TStringList; const AExternalMessageHandler: HWND; const AMsg_FSTDone: Cardinal);
begin
inherited Create(TRUE);
FExternalMessageHandler := AExternalMessageHandler;
FMsg_FSTDone := AMsg_FSTDone;
FPath := IncludeTrailingPathDelimiter(APath);
FIgnorePaths := TStringList.Create;
FIgnorePaths.Assign(AIgnorePaths);
FExtensions := TStringList.Create;
FExtensions.Assign(AAllowedExtensions);
FFiles := TStringList.Create;
WriteLn(FPath, ' file search thread created.');
end;
destructor TFileSearchThread.Destroy;
begin
FExtensions.Free;
FIgnorePaths.Free;
WriteLn(FPath, ' file search thread destroyed.');
inherited;
end;
function TFileSearchThread.IsIgnoreDir(const ADir: String; out AKeepIgnoreCheck: Boolean): Boolean;
var
C1: Integer;
begin
AKeepIgnoreCheck := FALSE;
if not Assigned(FIgnorePaths) then
Exit(FALSE);
for C1 := 0 to FIgnorePaths.Count - 1 do
if AnsiStartsText(FIgnorePaths[C1], ADir) then
Exit(TRUE)
else
if not AKeepIgnoreCheck then
AKeepIgnoreCheck := AnsiStartsText(ADir, FIgnorePaths[C1]);
Exit(FALSE);
end;
procedure TFileSearchThread.Execute;
var
search_rec : TSearchRec;
dirs : TStringList;
dirs_nocheck : TStringList;
dir : String;
ignore_check : Boolean;
ignore_check_tmp: Boolean;
newdir : String;
begin
dirs := TStringList.Create;
try
dirs_nocheck := TStringList.Create;
try
dirs.Add(FPath);
while (not Terminated) and
((dirs.Count > 0) or (dirs_nocheck.Count > 0)) do
begin
ignore_check := dirs.Count > 0;
if ignore_check then
begin
dir := dirs[0];
dirs.Delete(0);
end
else
begin
dir := dirs_nocheck[0];
dirs_nocheck.Delete(0);
end;
if (not ignore_check) or
(not IsIgnoreDir(LowerCase(dir), ignore_check)) then
if FindFirst(dir + '*', faAnyFile, search_rec) = 0 then
try
repeat
if (search_rec.Attr and faDirectory) = 0 then
begin
if FExtensions.IndexOf(ExtractFileExt(search_rec.Name)) <> -1 then // crashes here
FFiles.Add(dir + search_rec.Name);
end
else
if (search_rec.Name <> '.') and (search_rec.Name <> '..') then
begin
newdir := dir + search_rec.Name + '\';
if not ignore_check then
dirs_nocheck.Add(newdir)
else
if not IsIgnoreDir(LowerCase(newdir), ignore_check_tmp) then
if ignore_check_tmp then
dirs.Add(newdir)
else
dirs_nocheck.Add(newdir);
end;
until (Terminated) or (FindNext(search_rec) <> 0);
finally
FindClose(search_rec);
end;
end;
finally
dirs_nocheck.Free;
end;
finally
dirs.Free;
end;
PostMessage(FExternalMessageHandler, FMsg_FSTDone, NativeUInt(pointer(self)), 0);
end;
end.
(我知道我不会在析构函数中释放FFiles,但那是因为我想避免数据重复,所以我在线程销毁后将它传递给另一个继续使用它的对象)
以及创建线程的过程:
procedure CreateFileSearchThread(const APath: String);
const
{$I ignore_dirs.inc}
{$I allowed_extensions.inc}
var
ignore_dirs_list, allowed_exts_list: TStringList;
file_search_thread : TFileSearchThread;
C1 : Integer;
begin
ignore_dirs_list := TStringList.Create;
try
ignore_dirs_list.Sorted := TRUE;
ignore_dirs_list.CaseSensitive := FALSE;
ignore_dirs_list.Duplicates := dupIgnore;
for C1 := Low(IGNORE_DIRS) to High(IGNORE_DIRS) do
ignore_dirs_list.Add(LowerCase(ExpandEnvStrings(IGNORE_DIRS[C1])));
allowed_exts_list := TStringList.Create;
try
allowed_exts_list.Sorted := TRUE;
allowed_exts_list.CaseSensitive := FALSE;
allowed_exts_list.Duplicates := dupIgnore;
for C1 := Low(ALLOWED_EXTS) to High(ALLOWED_EXTS) do
allowed_exts_list.Add('.' + ALLOWED_EXTS[C1]);
file_search_thread := TFileSearchThread.Create(APath, ignore_dirs_list, allowed_exts_list, FMessageHandler, FMsg_FSTDone);
FFileSearchThreads.Add(file_search_thread);
file_search_thread.Start;
finally
allowed_exts_list.Free;
end;
finally
ignore_dirs_list.Free;
end;
end;
我只是通过调用FFileSearchThreads.Free 来销毁线程,然后它应该释放它的对象,因为OwnObjects 设置为TRUE。 FFileSearchThreads 属于 TObjectList<TFileSearchThread> 类型。
【问题讨论】:
标签: multithreading delphi