Поток Delphi не работает

Я пытаюсь найти все файлы во всех подпапках, поэтому это занимает много времени, и приложение перестает отвечать, поэтому я использовал Thread (это первая работа с Threads). Я читал об этом и нашел этот способ для создания и выполнения потоков, но ничего не происходит, когда я вызываю поток, и я не понимаю, почему я не мог использовать добавленные компоненты на главной форме, мне пришлось снова объявить его?
что я здесь упускаю?

type
  TSearchThread = class(TThread)
  private
    { Private declarations }
  protected
    procedure Execute; override;
  end;

procedure AddAllFilesInDir(const Path: string; ListBox:TsListBox);
var
  SR: TSearchRec;
  I: Integer;
begin
  if FindFirst(IncludeTrailingBackslash(Path) + '*.*', faAnyFile or faDirectory, SR) = 0 then
    try
      repeat
        if (SR.Attr and faDirectory) = 0 then
            ListBox.Items.Add(Path+'\'+SR.Name)
        else if (SR.Name <> '.') and (SR.Name <> '..') then
          AddAllFilesInDir(IncludeTrailingBackslash(Path) + SR.Name, ListBox);
          Form1.sPanel2.Caption := Path+'\'+SR.Name;
          Form1.sPanel2.Refresh;
          ListBox.Refresh;
      until FindNext(Sr) <> 0;
    finally
      FindClose(SR);
    end;
end;

procedure TSearchThread.Execute;
var FileList: TsListBox;
    I: Integer;
    {Here I had to re-declare objects}
    sDirectoryEdit1: TsDirectoryEdit;
    sListBox1: TsListBox;
begin
      FileList := TsListBox.Create(nil);
      FileList.Parent := sListBox1;
      FileList.Visible := False;
      AddAllFilesInDir(sDirectoryEdit1.Text+'\', FileList);
      for I := 0 to FileList.Count -1 do
      if sListBox1.Items.IndexOf(FileList.Items.Strings[I]) = -1 then
      sListBox1.Items.Add(FileList.Items.Strings[I]);
      FileList.Clear;
end;


procedure TForm1.sDirectoryEdit1Change(Sender: TObject);
begin
    TSearchThread.Create(False);
end;

person Delphi Lover    schedule 20.10.2015    source источник
comment
Это было описано здесь много раз. Вы не можете получить доступ к объектам VCL из потока. Используйте TThread.Queue или TThread.Synchronize.   -  person David Heffernan    schedule 20.10.2015
comment
Вам нужно узнать о нитях. Вам нужно переопределить Execute TThread.   -  person MartynA    schedule 20.10.2015
comment
Не очередной давайте нарушим все правила, обратившись к элементам управления пользовательского интерфейса напрямую из моей темы. Существуют буквально десятки существующих вопросов Delphi о том, как использовать потоки. Найдите их и найдите пример, который показывает, как правильно их использовать. Ваш первый признак того, что что-то пошло не так, должен был появиться, когда вам пришлось повторно объявить все ваши элементы управления пользовательского интерфейса внутри метода Execute потока; Вы должны были сразу понять, что делаете что-то очень неправильное.   -  person Ken White    schedule 20.10.2015
comment
@JensBorrisholt на существующие вопросы все еще нет обязательного ответа, поэтому я не закрывал их, должен ли я закрыть вопрос, даже если на него нет ответа?   -  person Delphi Lover    schedule 20.10.2015
comment
@JensBorrisholt Пожалуйста, не приставайте таким образом к спрашивающему. Аскер задал 4 вопроса, включая этот. На один вопрос есть принятый ответ. Другой вопрос не имеет ответа. И на последний вопрос есть ответы, но мне далеко не ясно, следует ли их принимать.   -  person David Heffernan    schedule 20.10.2015
comment
@Jens Возможно, ты ошибся. Несколько раз в прошлом, когда я пытался урезонить вас по поводу ваших ответов, вы не стремились слушать. Итак, мне кажется правдоподобным, что в вашем ответе есть недостаток, которого вы еще не видите. В прошлый раз, когда я пытался связаться с вами, вы обвинили меня в плагиате и оскорбили меня.   -  person David Heffernan    schedule 20.10.2015
comment
Если у вас есть время .. Вперед.   -  person Jens Borrisholt    schedule 20.10.2015
comment
@Jens: Где в справочном центре сказано, что вы должны закрыть все существующие вопросы, прежде чем сможете задать новый? На самом деле, где вообще сказано, что вы должны закрывать какие-либо вопросы?   -  person Ken White    schedule 20.10.2015
comment
@JensBorrisholt спасибо за интерес, но ваш комментарий ни к чему не приводит, он бесполезен   -  person Delphi Lover    schedule 20.10.2015
comment
@DavidHeffernan Я прочитаю о TThread.Synchronize и попробую, спасибо.   -  person Delphi Lover    schedule 20.10.2015
comment
Что вам нужно изучить, так это концепцию многослойности. Ваш поток не должен ничего знать о пользовательском интерфейсе. Определите его в отдельном слое, низкоуровневом слое. Поместите его в отдельный блок. Спроектируйте его так, чтобы его можно было использовать повторно.   -  person David Heffernan    schedule 20.10.2015
comment
@KenWhite Я прочитал и просмотрел несколько примеров, прежде чем задать свой вопрос, но я не заметил, что мы не можем использовать объекты VCL в потоках, но если бы я повторно объявил объекты в потоке, то невозможно было бы получить вывод потока в реальные объекты потом?   -  person Delphi Lover    schedule 20.10.2015
comment
Как я уже сказал в своем предыдущем комментарии, ищите здесь delphi thread. Есть много предыдущих примеров, демонстрирующих правильный способ обновления элементов управления графического интерфейса из потока.   -  person Ken White    schedule 20.10.2015
comment
@JensBorrisholt: Я согласен с DavidH, и у вас странное, агрессивное отношение к людям, которым вы отвечаете (например, вы ругаете их, говоря, что это их вина, если ваше решение не работает для них). Дайте ему отдохнуть и/или повзрослеть.   -  person MartynA    schedule 20.10.2015
comment
@DelphiLover Вам не нужно и не нужно, чтобы поток знал о пользовательском интерфейсе. Поток должен получить спецификацию задачи, каталог для запуска, рекурсивность, какие типы файлов возвращать и т. д. Затем он должен перечислить файлы/каталоги. Когда он находит значение, он должен дать его. Это может включать в себя постановку в очередь в пользовательском интерфейсе. Но это было бы дорого. Лучше сгруппировать их и поставить в очередь, скажем, 0,1 с.   -  person David Heffernan    schedule 20.10.2015
comment
@DavidHeffernan, можете ли вы проверить мой обновленный ответ, и если он правильный и не нарушает условия stackoverflow (даже если этого никогда не было), восстановить его?   -  person Master    schedule 24.10.2015
comment
@Мастер, я не могу его восстановить   -  person David Heffernan    schedule 24.10.2015
comment
@DavidHeffernan хорошо, спасибо   -  person Master    schedule 24.10.2015


Ответы (1)


Хорошо, позвольте мне попробовать:

Сначала новая версия вашей темы:

uses
  IOUtils;

type
  TFileFoundEvent = procedure(const Path: string; const SearchRec: TSearchRec) of object;

  TSearchThread = class(TThread)
  private
    FPath: string;
    FSearchRec: TSearchRec;
    FFileFoundEvent: TFileFoundEvent;
  protected
    procedure Execute; override;
  public
    Constructor Create(const aPath: string; aFileFoundEvent: TFileFoundEvent); reintroduce;
  end;

  { TSearchThread }

constructor TSearchThread.Create(const aPath: string; aFileFoundEvent: TFileFoundEvent);
begin
  // Create the Thread non suspended
  inherited Create(false);

  // Copy parameters to local members.
  FFileFoundEvent := aFileFoundEvent;
  FPath := aPath;

  // Make the sure the thread frees itself after execution
  FreeOnTerminate := True;
end;

procedure TSearchThread.Execute;
var
  FilterPredicate: TDirectory.TFilterPredicate;
begin
  // FilterPredicate is an in-place anonymous method to be called each time the TDirectory.GetFiles finds a file
  FilterPredicate := function(const Path: string; const SearchRec: TSearchRec): Boolean
    begin
      // Since we can not access from within Synchronize we need to copy iot to a member of the class
      FSearchRec := SearchRec;

      // You cannot access VCL objects directly from a thread.
      // So you need to call Syncronize
      // For more info look in the online help
      // http://docwiki.embarcadero.com/Libraries/Seattle/en/System.Classes.TThread.Synchronize
      Synchronize(nil,
        procedure
        begin
          FFileFoundEvent(FPath, FSearchRec);
        end);

      Result := True;
    end;

  // Do the search
  TDirectory.GetFiles(FPath, TSearchOption.soTopDirectoryOnly, FilterPredicate)
end;

Основное отличие заключается в том, что я передаю процедуру обратного вызова конструктору потока. И потому я использую TDirectory.GetFiles для поиска файлов. Вы найдете TDirectory.GetFiles в IOUtils

Затем вам нужно использовать его: поместите список в свой список, а затем вызовите его следующим образом:

Определение формы:

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    procedure FormCreate(Sender: TObject);
  private
    procedure FileFoundEvent(const Path: string; const SearchRec: TSearchRec);
  public
    { Public declarations }
  end;

...

implementation

procedure TForm1.FileFoundEvent(const Path: string; const SearchRec: TSearchRec);
begin
  ListBox1.Items.Add(SearchRec.Name);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  TSearchThread.Create(ExtractFilePath(Application.ExeName), FileFoundEvent);
end;

Если вы не хотите видеть текущие результаты поиска, а хотите немного ускориться, вы можете создать версию потока поиска, которая даст вам результат сразу:

uses
  IOUtils;

type
  TSearchThread = class(TThread)
  private
    FSearchPath: String;
    FResultBuffer: TStrings;
  protected
    procedure Execute; override;
  public
    constructor Create(const aSearchPath: string; aResultBuffer: TStrings); overload;
  end;

constructor TSearchThread.Create(const aSearchPath: string; aResultBuffer: TStrings);
begin
  inherited Create(false);
  FSearchPath := IncludeTrailingPathDelimiter(aSearchPath);
  FResultBuffer := aResultBuffer;
  FreeOnTerminate := True;
end;

procedure TSearchThread.Execute;
var
  FBuffer: TStringlist;
  Filename: String;
begin
  Synchronize(nil,
    procedure
    begin
      FResultBuffer.Text := 'Searching ' + FSearchPath;
    end);

  FBuffer := TStringlist.Create;
  for Filename in TDirectory.GetFiles(FSearchPath, TSearchOption.soAllDirectories, nil) do
    FBuffer.Add(Filename);

  Synchronize(nil,
    procedure
    begin
      FResultBuffer.Assign(FBuffer);
    end);

  FreeAndNil(FBuffer);
end;

Этот поток вы должны вызывать немного по-другому.

Настройка формы такая же, как и раньше: список на форме.

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    procedure FormCreate(Sender: TObject);
  private
    Stopwatch: TStopwatch;
    procedure SearchThreadTerminate(Sender: TObject);
  public
    { Public declarations }
  end;

А дальше реализация:

procedure TForm1.FormCreate(Sender: TObject);
begin
  Stopwatch := TStopwatch.StartNew;

  with TSearchThread.Create('C:\Program Files (x86)\Embarcadero\', ListBox1.Items) do
    OnTerminate := SearchThreadTerminate;
end;

procedure TForm1.SearchThreadTerminate(Sender: TObject);
begin
  Stopwatch.Stop;
  Caption := 'Elapsed Milliseconds: ' + IntToStr(Stopwatch.ElapsedMilliseconds) + ' Files found: ' + IntToStr(ListBox1.Items.Count);
end;

Преимуществом этой версии является скорость. Обновление экрана происходит медленно, и первое решение обновляло экран для каждого найденного файла, а это решение обновляет экран только дважды.

Попробуйте.

person Jens Borrisholt    schedule 20.10.2015
comment
Хм, есть ли смысл выполнять работу в потоке, если вы не вызываете Begin/EndUpdate для элементов ListBox? - person MartynA; 20.10.2015
comment
@MartynA Эм, поддерживать реакцию приложения во время поиска? - person Jerry Dodge; 20.10.2015
comment
Буферизация в течение короткого периода времени перед синхронизацией данных предотвратит захлебывание основного потока при наличии большого количества файлов. - person LU RD; 20.10.2015
comment
@JerryDodge: Нет. Есть гораздо лучшие (и более простые) способы сделать это, чем в этом ответе. Представьте, что нужно добавить более 100 000 файлов... - person MartynA; 20.10.2015
comment
@MartynA правильно, если есть более 100 тысяч файлов, это замедлит процесс поиска, но графический интерфейс будет реагировать во время поиска. Я сделал обновленную версию, которая обновляет графический интерфейс только дважды. - person Jens Borrisholt; 21.10.2015