Невозможно завершить потоки

Я использую потоки в своем проекте. И я хочу немедленно убить и завершить поток.

пример:

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

    var
     Form1: TForm1;
     a:tthread;

    implementation

    {$R *.dfm}

    procedure test.Execute;
    begin

      Synchronize(procedure begin    
          form1.ProgressBar1.position := 0;
          sleep(5000);
          form1.ProgressBar1.position := 100;    
      end
      );

    end;

   procedure TForm1.btn_startClick(Sender: TObject);
   begin
     a:=test.Create(false);
   end;

   procedure TForm1.btn_stopClick(Sender: TObject);
   begin
     terminatethread(a.ThreadID,1);  //Force Terminate
   end;

Но когда я нажимаю на btn_stop (после нажатия на btn_start), поток не останавливается. Итак, как можно немедленно остановить этот поток?

Кстати, a.terminate; тоже не сработало.

Спасибо.


person Sky    schedule 14.08.2013    source источник
comment
Ваш основной поток спит в течение 5 секунд, поэтому вы не можете ожидать, что он что-то сделает немедленно. Кроме того, есть разница между ThreadID и Handle. Функция TerminateThread ожидает поток Handle, а не ThreadID.   -  person TLama    schedule 14.08.2013
comment
@TLama 1) Если я закрою приложение за эти 5 секунд, как Windows закроет этот поток? 2) Это образец. Я не могу завершить ни один поток даже без sleep... И о вашем редактировании: Так как я могу его закрыть? И как я могу найти thread handle?   -  person Sky    schedule 14.08.2013
comment
Назовите это как TerminateThread(a.Handle, 1);. Вы проходили там ThreadID, что отличается от Handle. Но обратите внимание, что это неправильный способ завершения потоков.   -  person TLama    schedule 14.08.2013
comment
@TLama Большое спасибо! просто получилось ;)   -  person Sky    schedule 14.08.2013
comment
Лучшие практики говорят, что вы должны смотреть на свойство Terminated, находясь в цикле или выполняя код (в Execute)... а затем Terminate...   -  person House of Dexter    schedule 14.08.2013
comment
Я хочу немедленно закрыть и закрыть поток. Нет, на самом деле нет.   -  person David Heffernan    schedule 14.08.2013


Ответы (2)


Это полное неправильное использование рабочего потока. Вы делегируете всю работу потока основному потоку, делая рабочий поток бесполезным. Вместо этого вы могли бы использовать простой таймер.

Вместо этого правильное использование рабочего потока будет выглядеть примерно так:

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

var
  Form1: TForm1;
  a: test = nil;

implementation

{$R *.dfm}

procedure test.Execute;
var
  I: integer
begin
  Synchronize(
    procedure begin    
      form1.ProgressBar1.Position := 0;
    end
  );

  for I := 1 to 5 do
  begin
    if Terminated then Exit;
    Sleep(1000);
    if Terminated then Exit;
    Synchronize(
      procedure begin
        Form1.ProgressBar1.Position := I * 20;
      end
    );
  end;

  Synchronize(
    procedure begin
      form1.ProgressBar1.Position := 100;    
    end
  );
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  btn_stopClick(nil);
end;

procedure TForm1.btn_startClick(Sender: TObject);
begin
  if a = nil then
    a := test.Create(False);
end;

procedure TForm1.btn_stopClick(Sender: TObject);
begin
  if a = nil then Exit;
  a.Terminate;
  a.WaitFor;
  FreeAndNil(a);
end;
person Remy Lebeau    schedule 14.08.2013
comment
Что делает a.WaitFor;? И можно ли использовать TerminateThread(a.Handle, 1); вместо a.Terminate;? Спасибо за код. - person Sky; 14.08.2013
comment
Вы делегируете всю работу потока основному потоку Почему? Из-за Synchronize? - person Sky; 14.08.2013
comment
Да, из-за синхронизации. Это то, что делает синхронизация. Он запускает данный код в основном потоке. Если ваш основной поток выполняет этот код, то очевидно, что он не делает ничего одновременно, в частности, не обрабатывает события нажатия кнопки. - person Rob Kennedy; 14.08.2013
comment
Нет, вы не можете использовать TerminateThread. Прочтите документацию, чтобы узнать, почему нет. - person David Heffernan; 14.08.2013
comment
@Sky: WaitFor() ждет завершения потока. НЕ ИСПОЛЬЗУЙТЕ TerminateThread(), за исключением случаев, когда у вас нет другого выбора (что вы и делаете). Это грубое завершение, которое не позволяет потоку очиститься после себя (ресурсы, блокировки и т. д.), что может быть очень опасно. Всегда позволяйте потокам завершать себя изящно, когда это возможно. Именно для этого предназначены метод TThread.Terminate() и свойство TThread.Terminated. - person Remy Lebeau; 14.08.2013

Проблема в том, что поток ожидает, используя Sleep. Этот метод удерживает поток в спящем режиме в течение указанного времени, независимо от того, что происходит вокруг него. Чтобы иметь возможность «разорвать сон», вы должны использовать событие. Код следует изменить на этот:

procedure test.Execute;
begin
  Synchronize(procedure begin    
      form1.ProgressBar1.position := 0;
  end);
  Event.WaitFor(5000);
  if not IsTerminated then
    Synchronize(procedure begin    
        form1.ProgressBar1.position := 100;    
    end);
end;

Событие должно быть создано и уничтожено следующим образом:

constructor test.Create(aCreateSuspended: Boolean);
begin
  inherited;
  Event := TSimpleEvent.Create;
end;

destructor test.Destroy;
begin
  FreeAndNil(Event);
  inherited;
end;

Чтобы остановить поток, код:

procedure TForm1.btn_stopClick(Sender: TObject);
begin
  a.Terminate;
end;

Но простой вызов Terminate не сигнализирует о Событии, поэтому нам нужно переопределить Terminate:

procedure test.Terminate;
begin
  inherited;
  Event.SetEvent;
end;

Вызов SetEvent будет сигнализировать о событии, поэтому он разбудит поток. Выполнение продолжается в следующей строке, которая проверяет завершение потока и решает, выполнять вторую часть кода или нет.

person AlexSC    schedule 14.08.2013
comment
Вам нужен деструктор, чтобы уничтожить Event, и вы должны сначала вызвать inherited в конструкторе. - person David Heffernan; 14.08.2013
comment
@DavidHeffernan: порядок inherited в конструкторе не имеет значения. В отличие от C++, где конструкторы-предки должны вызываться первыми, в Delphi такого ограничения нет. Конструктор-предок может быть вызван в начале, в конце или даже в середине производного конструктора. - person Remy Lebeau; 14.08.2013
comment
@AlexSC: да, использование ожидаемого события - лучший выбор для сна. Я не хотел смущать Скай этой деталью в своем ответе, поскольку он / она, очевидно, новичок в программировании потоков. - person Remy Lebeau; 14.08.2013
comment
@DavidHeffernan: вызов, унаследованный до создания события, может позволить потоку выполниться до выхода из TSimpleEvent, это зависит от того, что было передано в aCreateSuspended, поэтому я решил сделать это, чтобы убедиться, что объект существует при выполнении метода Execute. Что касается деструктора, я согласен, и я отредактирую сообщение, но это то, что спрашивающий должен знать, поскольку это базовая практика Delphi. - person AlexSC; 14.08.2013
comment
@Remy Я знаю это, конечно. Дело в том, что по соглашению мы пишем конструкторы, сначала вызывая унаследованные. Автор здесь, по-видимому, думает, что вы должны сделать это последним в потоке, потому что автор считает, что поток начнет работать, как только вызов унаследованного вернется. Вы предполагаете, что это хорошая практика - вызывать inherited в середине кода вашего конструктора? Это был бы плохой совет. - person David Heffernan; 14.08.2013
comment
@Alex У вас есть основное недоразумение. Поток не запускается до AfterConstruction. Так что порядок, который я предлагаю, работает отлично. В Delphi 5 то, что вы сказали, было бы правдой, но TThread был изменен в Delphi 6. - person David Heffernan; 14.08.2013
comment
@ Реми прав, что в этом случае порядок не имеет значения. Однако когда вы читаете код, скажем, при просмотре кода, нестандартный порядок вызовет недоумение и беспокойство. Я был на 99% уверен, что вы сделали это из-за очень распространенного непонимания того, когда запускается поток ОС. Отсюда и мой комментарий. - person David Heffernan; 14.08.2013
comment
@DavidHeffernan: ты знаешь это. Я знаю это. Не все остальные это знают. Вызов унаследованного конструктора в середине возможен (на самом деле, я видел классы Borland/Codegear/Embarcadero, которые действительно это делают), но я не предлагал делать это на практике. - person Remy Lebeau; 15.08.2013