2013-10-18 123 views
4

我一直在编写单元来搜索以指定扩展名结尾的文件,并且能够跳过搜索指定的目录。该数据分别包含在FExtensionsFIgnorePathsTStringList对象中。TStringList.IndexOf()导致线程崩溃

然而,大约1出10次运行时,线程崩溃与以下情况除外:

thread crash exception

调试位后,我隔离在搜索线程此线作为碰撞的原因:

if FExtensions.IndexOf(ExtractFileExt(search_rec.Name)) <> -1 then 

在我致电IndexOf()之前,我试过做Assigned(FExtensions)检查,但没有消除这次事故。如果我评论这一行,线程压力测试可以正常工作(以100ms的间隔创建/销毁它)。我知道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; 

我摧毁threa d只需调用FFileSearchThreads.Free,然后应该释放它的对象,因为OwnObjects设置为TRUEFFileSearchThreadsTObjectList<TFileSearchThread>类型。

回答

4

我灭线只需调用FFileSearchThreads.Free, 然后应该释放它的对象,因为OwnObjects是 设置为true。 FFileSearchThreads是TObjectList 类型。

等一下。你告诉你的线程Terminate()之前和WaitFor()他们来完成,你呢?如果没有,那么你真的应该这样做!

线程不仅包含存储在TThread实例中的数据。它分配一系列与操作系统线程对象关联的系统资源,这表示单个流/执行上下文。这些资源必须正确解除分配并且需要停止执行,然后才能在内部OS对象周围释放()Delphi对象。

这可能值得考虑FreeOnTerminate := TRUE,基本上让线程自己完成清理工作。您仍然负责启动此过程,通常通过设置共享的全局标志或实例或类似的东西。这样你就可以解耦这些东西并摆脱线程列表。两种方法都有他们的专业和客户。

+1

我相信'TThread.Destroy()'已经做到了这一点? ('Terminate()'然后'WaitFor()')。检查'System.Classes.TThread.Destroy()'析构函数代码。 –

+0

好吧,所以我刚刚尝试过.Terminate()和.WaitFor(),而不是释放对象列表,它的工作原理!但是'TThread'的'.Destroy()'中的相同代码的目的是什么,以及为什么它不以那种方式工作? –

+0

您首先释放您的数据结构,然后调用'inherited Destroy'。此外,根据我的感觉,DTOR不是等待线程终止的正确位置,恕我直言,VCL代码只是为了避免显而易见。但这是我个人的看法。 – JensG

1

只是为了完整性,这里是正在发生的事情:

  1. Execute方法是使用FIgnorePathsFExtensions对象。
  2. 析构函数破坏这些对象,而Execute仍在飞行中。
  3. 然后Execute在这些对象被释放后访问这些对象。繁荣!

看看你的线程的析构函数:

destructor TFileSearchThread.Destroy; 
begin 
    FExtensions.Free; 
    // Execute is still active at this point 

    FIgnorePaths.Free; 
    // and still active here 

    inherited;  
    // this calls Terminate and WaitFor, and that brings matters to a close, 
    // but not before the thread has opportunity to access the objects which 
    // you just destroyed 
end; 

你需要重新设计的东西,以确保该线程不使用任何对象,他们已被销毁后。

+0

是的,我不知道的是'.Destroy()'的最高级别实际上叫做BEFORE线程从'.Execute()'退出。 –

+1

您可以在DTOR的顶部投入Terminate和WaitFor –