Товарищи, я пишу плагин для миранды (без использования визуальных компонент).
В нем есть два основных потока:
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 cyclebegin
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 того, чего уже нет?)
Что за метод такой? Я думал поток останавливается методом Suspend().
procedure UpdateDataDestroy();
begin
with ThrIDDatUpdate do begin
if ( not Terminated ) then TerminateAndWaitFor;
end;
end;
Не очень силен в потоках но попробуй так. Да, и помнится специально освобождать память выделенную под поток не обязательно, потому что после метода Execute автоматически вызывается стандартную процедуру Delphi EndThread(), которая вызывает функцию Win32 API ExitThread(), по завершении работы которой поток перестанет существовать и вся выделенная ему память будет корректно освобождена.
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 не работает корректно.
А что мешает родителю, перед завершением послать сообщение, что все, я подыхаю?
А вообще вы делаете очень странно, делаете 2 параллельных нити и, учитывая задачу, не заботитесь о синхронизации.
H>А что мешает родителю, перед завершением послать сообщение, что все, я подыхаю?\
через PostMessage? или есть другие способы?
H>А вообще вы делаете очень странно, делаете 2 параллельных нити и, учитывая задачу, не заботитесь о синхронизации.
ну, синхронизация тут не так важна.
Не надо останавливать потоки, перезапускать — это потенциальный источник трудно обнаруживаемых ошибок. Когда в потоке нет надобности, его надо отправлять спать вызовом WaitForSingleObject или WaitForMultipleObjects, а когда ему нужно работать — выводить из спячки подходящим объектом синхронизации, обычно это событие Windows (CreateEvent и т.д.)
То есть стандартный метод TThread.Execute потока обычно должен выглядеть примерно так:
...
while not Terminated do begin
... // подготовили аргументы для WaitForMultipleObjectscase 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