Потоки
От: Аноним  
Дата: 11.04.09 15:29
Оценка:
Товарищи, я пишу плагин для миранды (без использования визуальных компонент).
В нем есть два основных потока:
1) ThrIDConnect — для соединения с сервером
2) ThrIDDataUpdate — для обновления данных
Потоки создаются следующим образом:
type
  TThreadConnect = class(TThread)
  private
     { Private declarations }
  protected
    procedure Execute; override;
  end;
  TThreadDataUpdate = class(TThread)
  private
    { Private declarations }
  protected
    procedure Execute; override;
  end;

var
  ThrIDConnect: TThreadConnect;
  ThrIDDataUpdate: TThreadDataUpdate;

...
  if not Assigned(ThrIDConnect) then
  begin
    ThrIDConnect := TThreadConnect.Create(True);
    ThrIDConnect.FreeOnTerminate := True;
    ThrIDConnect.Resume;
  end;
...
  if not Assigned(ThrIDDataUpdate) then
    ThrIDDataUpdate := TThreadDataUpdate.Create(False);


Ну и в потоках происходит следующее:

procedure TThreadConnect.Execute;
begin
  ...
  // если необходимо перейти в оффлайн в миранде,
  // то нужно остановить поток
  if (not ThrIDConnect.Terminated) then
  begin
    UpdateDataDestroy(); // останавливает поток ThrIDDataUpdate
    ...
  end;
  ...
  ThrIDConnect := nil;
end;

procedure TThreadDataUpdate.Execute;
begin
  ...
  while true do // never ending cycle
  begin
    try
      if ThrIDDataUpdate.Terminated = True then
        break;
      // тут длительная обработка данных
      if ThrIDDataUpdate.Terminated = True then
        break;
      // тут следующая, другая длительная обработка данных
      Sleep(1000);
    except
      if ThrIDDataUpdate.Terminated = True then
        break;     
    end;
  end;
  ...
end;

Проблема возникает с остановкой потоков:
// процедура остановки потока, вызывается из TThreadConnect.Execute 
procedure UpdateDataDestroy();
begin
  if Assigned(ThrIDDataUpdate) then
    begin
      ThrIDDataUpdate.Terminate;
      ThrIDDataUpdate.WaitFor; // есть шанс, что к этому моменту поток ThrIDDataUpdate уже terminated
      FreeAndNil(ThrIDDataUpdate);
    end;
end;

// данная функция миранды определяет готов ли наш плагин к выходу из миранды
function OkToExit: Integer;
begin
  if Assigned(ThrIDConnect) then
    ThrIDConnect.Terminate; // посылаем команду остановить поток, дождаться реального завершения не так важно
  if Assigned(ThrIDDataUpdate) then
    ThrIDDataUpdate.Terminate; // тут бы дождаться завершения, но мы этого не делаем
  Result := 0;
end;

Помимо проблем, описанных в комментариях выше, происходит следующее при выходе из миранды (в порядке очередности):
1) запускается процедура OkToExit, отправляются команды остановить потоки ThrIDConnect и ThrIDDataUpdate;
2) миранда сама запускает TThreadConnect (для перехода оффлайн), if (not ThrIDConnect.Terminated) then срабатывает как true и потому снова запускается UpdateDataDestroy => миранда зависает (ждет WaitFor того, чего уже нет?)

Как поправить проблемы, описанные выше?
потоки delphi завершение
Re: Потоки
От: Velh  
Дата: 11.04.09 20:40
Оценка:
А> UpdateDataDestroy(); // останавливает поток ThrIDDataUpdate

Что за метод такой? Я думал поток останавливается методом Suspend().

procedure UpdateDataDestroy();
begin
  with ThrIDDatUpdate do begin
    if ( not Terminated ) then TerminateAndWaitFor;
  end;
end;


Не очень силен в потоках но попробуй так. Да, и помнится специально освобождать память выделенную под поток не обязательно, потому что после метода Execute автоматически вызывается стандартную процедуру Delphi EndThread(), которая вызывает функцию Win32 API ExitThread(), по завершении работы которой поток перестанет существовать и вся выделенная ему память будет корректно освобождена.
w
Re[2]: Потоки
От: LA_  
Дата: 12.04.09 09:41
Оценка:
V> Что за метод такой? Я думал поток останавливается методом Suspend().
Думаю, что Вы уже поняли, что это такое :)
Это просто своя процедура, в которой останавливается поток.

V>
V>procedure UpdateDataDestroy();
V>begin
V>  with ThrIDDatUpdate do begin
V>    if ( not Terminated ) then TerminateAndWaitFor;
V>  end;
V>end;
V>

TerminateAndWaitFor моей Дельфи 7, увы, неизвестен.

V> Да, и помнится специально освобождать память выделенную под поток не обязательно, потому что после метода Execute автоматически вызывается стандартную процедуру Delphi EndThread(), которая вызывает функцию Win32 API ExitThread(), по завершении работы которой поток перестанет существовать и вся выделенная ему память будет корректно освобождена.

Если я не ошибаюсь, то это работает таким образом лишь тогда, когда поток запускается со свойством FreeOnTerminate := True; но в этом случае WaitFor не работает корректно.
Re: Потоки
От: LA_  
Дата: 13.04.09 13:31
Оценка:
товарищи, неужели больше никто не знает? ;)
Re: Потоки
От: Hades  
Дата: 16.04.09 07:34
Оценка:
А что мешает родителю, перед завершением послать сообщение, что все, я подыхаю?
А вообще вы делаете очень странно, делаете 2 параллельных нити и, учитывая задачу, не заботитесь о синхронизации.
Re[2]: Потоки
От: LA_  
Дата: 20.04.09 15:14
Оценка:
H>А что мешает родителю, перед завершением послать сообщение, что все, я подыхаю?\
через PostMessage? или есть другие способы?

H>А вообще вы делаете очень странно, делаете 2 параллельных нити и, учитывая задачу, не заботитесь о синхронизации.

ну, синхронизация тут не так важна.
Re: Потоки
От: wallaby  
Дата: 20.04.09 18:45
Оценка: 2 (1)
Здравствуйте, Аноним, Вы писали:

[skipped]

А>Как поправить проблемы, описанные выше?


Не надо останавливать потоки, перезапускать — это потенциальный источник трудно обнаруживаемых ошибок. Когда в потоке нет надобности, его надо отправлять спать вызовом WaitForSingleObject или WaitForMultipleObjects, а когда ему нужно работать — выводить из спячки подходящим объектом синхронизации, обычно это событие Windows (CreateEvent и т.д.)

То есть стандартный метод TThread.Execute потока обычно должен выглядеть примерно так:
...
while not Terminated do begin
  ... // подготовили аргументы для WaitForMultipleObjects
  case WaitForMultipleObjects(3, ...) of
    WAIT_OBJECT_0: ... // сделали одно
    WAIT_OBJECT_0 + 1: ... // сделали другое
    WAIT_OBJECT_0 + 2: Exit; // завершили поток
  end;
  ...
end;

Чтобы поток выполнил какую-то работу, соответствующее событие переводится в сигнальное состояние (SetEvent), поток по этому событию просыпается, выходит на нужную ветку в case WaitForMultipleObjects, выполняет код этой ветки и снова засыпает. Такой поток нельзя завершить простым вызовом Terminate, нужно выводить его из спячки событием (в примере это WAIT_OBJECT_0 + 2)
Короче говоря, читайте Рихтера или другую книгу про потоки и объекты синхронизации в Win32 API (скорее всего достаточно будет событий), и будет вам счастье.
---
The optimist proclaims that we live in the best of all possible worlds; and the pessimist fears this is true
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.