Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2008.07.20;
Скачать: CL | DM;

Вниз

Невероятная работа потока   Найти похожие ветки 

 
Пробегал2....   (2008-06-02 04:28) [0]

Я уже не знаю что делать, столкнулся с необъяснимым для меня глюком...

В общем, есть приложение-сервис, модуль Forms не подключен, управление сервисом через WEB. Структура сервиса реализована ручками, то есть всяких TServiceApplication (или как там) нету. Циклов выборки сообщения тоже нету... Даже не знаю что еще сказать.

Возникла проблема с одним классом, наследником от TThread. В Execute методе выставляется FreeOnTerminate в True, вот метод Destroy этого класса:

destructor TCallback.Destroy;
var
 i: integer ;
 List: TList ;
begin
 List := FStartClientList.LockList ;
 try
   for i := 0 to List.Count - 1 do
     Dispose(PStartClientTask(List[i]));
 finally
   FStartClientList.UnlockList ;
   FStartClientList.Free ;
 end;
 AddToLog("10");
 inherited;
 // AddToLog("11");
end;


Также есть метод Close:

procedure TCallback.Close;
begin
 FStartClientList.LockList ;
 try
   Terminate ;
   SetEvent(FWork) ;
 finally
   FStartClientList.UnlockList ;
 end;
end;


А вот кусок кода, в котором этот класс завершается:

 CallHandle := FCallback.Handle ;
 FCallback.Close ;
 AddToLog("1");
 WaitForSingleObject(CallHandle, INFINITE) ;  // <---- ВОТ ЗДЕСЬ ИНОГДА ЗАВИСАЕТ
 SUO_AddToLog("2");


Проблема в том, что иногда (далеко не всегда) на указанном месте происходит бесконечное ожидание завершения потока. В логе можно видеть: 10, 1.
Получается, дело в потоке точно дошло до конца метода Destroy, но вот двойки в логе не появляется, то есть поток так и не завершается!!! Но ГДЕ он может "застрять"?! Где-то во внутренностях VCL?!

Если концовку метода TCallback.Destroy переписать так:

//AddToLog("10");
inherited;
AddToLog("11");


То в логе можно увидеть: 1, 11.

Если же написать так:

AddToLog("10");
inherited;
AddToLog("11");


то у меня ни разу не получилось поймать зависание... Из этого думаю следует, что дело во времени, если нагрузить поток выводом в лог - то все ок. Самое легкое поймать глюк, если оставить вариант вывода 11, тогда виснет в половине случаев где-то. Если сделать вариант с выводом 10 - то виснет где-то раз в 5 реже, но виснет. Если выводить и 10, и 11 - как я говорил, глюк поймать не получилось.

У кого-нибудь есть хоть КАКИЕ ВАРИАНТЫ, почему так может происходить? Я уже просто не могу, мозг кипит, кроме как подозревать в некорретной работе или VCL, или Windows...


 
Slym ©   (2008-06-02 05:13) [1]

Есть подозрение что в WaitForSingleObject(CallHandle, INFINITE);
CallHandle -невалиден по причине его уничтожения (CloseHandle) ведь поток его сам FreeOnTerminate... а в основном потоке только призрак :)
если без переделки логики: попробуй DuplicateHandle в основном потоке и работай с "дублем"
или всеже FreeOnTerminate:=false
а то что "нагрузить поток выводом в лог" то тут все просто... поток притормаживается на вводе/выводе и основной поток успевает воспользоваться еще валидным хендлом


 
Loginov Dmitry ©   (2008-06-02 08:01) [2]

> FCallback.Close ;
> AddToLog("1");
> WaitForSingleObject(CallHandle, INFINITE) ;


А CallHandle - это вообще что такое? Приведено только его ожидание. А где остальная работа с ним?


 
ketmar ©   (2008-06-02 08:16) [3]

Удалено модератором


 
Пробегал2....   (2008-06-02 11:07) [4]

Slym ©   (02.06.08 5:13) [1]
CallHandle -невалиден по причине его уничтожения (CloseHandle)


ну точнее он может быть невалиден по причине завершения потока - с этим я согласен ;) Но CloseHandle я нигде не делаю.

Slym ©   (02.06.08 5:13) [1]
если без переделки логики: попробуй DuplicateHandle в основном потоке и работай с "дублем"


какой смысл дублировать Handle?!

Slym ©   (02.06.08 5:13) [1]
поток притормаживается на вводе/выводе и основной поток успевает воспользоваться еще валидным хендлом


логика есть... Но, какого хрена WaitForSingleObject ЗАВИСАЕТ над невалидным хендлом?! Она бы тут же вернула управление с ризоном, что "Неверный дескриптор".

Loginov Dmitry ©   (02.06.08 8:01) [2]
А CallHandle - это вообще что такое?


ты скопировал только часть кода. А еще есть одна строчка вверху и одна внизу.


 
DiamondShark ©   (2008-06-02 11:37) [5]


> Пробегал2....   (02.06.08 11:07) [4]


ну точнее он может быть невалиден по причине завершения потока - с этим я согласен ;) Но CloseHandle я нигде не делаю.

TThread.Destroy делает.


 
Пробегал2....   (2008-06-02 11:46) [6]

DiamondShark ©   (02.06.08 11:37) [5]
TThread.Destroy делает.


ну если в этом смысле - то да, делает.

Дело ведь доходит до вызова самим потоком (поскольку FreeOnTerminate в true) своего деструктора, метод Destroy явно исполняется... А это значит, что в потоковой функции:

function ThreadProc(Thread: TThread): Integer;

Доходит дело вот до этого:

if FreeThread then Thread.Free;  // <- СЮДА ТОЧНО ДОХОДИТ, деструктор же исполняется!
{$IFDEF MSWINDOWS}
   EndThread(Result);
{$ENDIF}


EndThread - это:

procedure EndThread(ExitCode: Integer);
begin
 ExitThread(ExitCode);
end;


В деструкторе TThread.Destroy написано:

destructor TThread.Destroy;
begin
 if (FThreadID <> 0) and not FFinished then
 begin
   Terminate;
   if FCreateSuspended then
     Resume;
   WaitFor;
 end;
{$IFDEF MSWINDOWS}
 if FHandle <> 0 then CloseHandle(FHandle);
{$ENDIF}
...


Тогда получается, что не просто поток завершился, а еще и CloseHandle точно сделали... Но тогда тем более WaitForSingleObject не должна висеть, ведь правильно? Ибо она висит над хендлом, который, во-первых, закрыли, да и еще сам объект ядра на который указывает этот хендл - уничтожен! Ну как тогда висит WaitForSingleObject?

Какова вероятность, что ДО вызова WaitForSingleObject поток успел завершиться и на его месте был создан какой-то другой поток (хотя потоков при завершении программы не создается!), и его хендл оказался таким же?! Ведь это бред имхо... Но другого объяснения я просто не вижу! ;(


 
oxffff ©   (2008-06-02 11:56) [7]

А здесь молчат.
http://forum.ixbt.com/topic.cgi?id=26:38333


 
Пробегал2....   (2008-06-02 11:59) [8]

oxffff ©   (02.06.08 11:56) [7]
А здесь молчат


я только вот что туда запостил.

Может у тебя есть какие-то соображения? Я лично в шоке...


 
oxffff ©   (2008-06-02 12:17) [9]

Код AddToLog где?


 
Сергей М. ©   (2008-06-02 12:18) [10]


> Пробегал2....   (02.06.08 11:59) [8]


Если оно у тебя "зависает", то одно из двух:

- либо ты что-то намудрил с крит.секциями
- либо твой поток ждет завершения самого себя


 
Mystic ©   (2008-06-02 12:39) [11]

> какой смысл дублировать Handle?!

Чтобы работать с ним из двух потоков и не беспокоится о том, что кто-нить другой сделал ему Close.


 
DiamondShark ©   (2008-06-02 12:50) [12]

Между прочим, WaitForSingleObject не завершается, если во время ожидания закрыть хэндл потока:

var
 gHandle: THandle;
 gLog: TextFile;

function ThreadProc1(Param: Pointer): Integer;
var
 m: MSG;
begin
 while true do Sleep(0);
end;

function ThreadProc2(Param: Pointer): Integer;
var
 rc: DWORD;
begin
 rc := WaitForSingleObject(gHandle, INFINITE);
 Rewrite(glog);
 writeln(gLog, rc);
 CloseFile(gLog);
end;

procedure TForm1.FormCreate(Sender: TObject);
var
 dummy: Cardinal;
begin
 AssignFile(gLog, "c:\log.txt");
 gHandle := BeginThread(nil, 0, ThreadProc1, nil, 0, dummy);
 BeginThread(nil, 0, ThreadProc2, nil, 0, dummy);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 CloseHandle(gHandle);
end;


 
Пробегал2....   (2008-06-02 13:55) [13]

oxffff ©   (02.06.08 12:17) [9]
Код AddToLog где?


ну это вызов функции во внешней DLL вообще... Я думаю он тут точно непричем. Можно вообще убрать AddToLog - зависания будут. Я специально его ввел, чтобы прояснить картину.

DiamondShark ©   (02.06.08 12:50) [12]
Между прочим, WaitForSingleObject не завершается, если во время ожидания закрыть хэндл потока


судя по всему ты прав!

Если на момент вызова WaitForSingleObject handle еще является валидным - то ошибки "неверный дескриптор" не происходит, поток засыпает.

После чего делается CloseHandle, но это не пробуждает поток, выхода из WaitForSingleObject (вот это на мой взгляд странно) не происходит. Но при этом "связь" с объектом ядра теряется, и когда объект ядра уничтожен - поток так и остается зависшим над WaitForSingleObject... Я честное слово - даже не подозревал!


 
Умищ   (2008-06-02 14:03) [14]

От закрытия дескриптора он не перестаёт бать валидным.
WaitForSingleObject завершится, когда связанный с дескриптором поток завершится.


 
Anatoly Podgoretsky ©   (2008-06-02 14:06) [15]

> Пробегал2....  (02.06.2008 13:55:13)  [13]

Да обычная проблема, как и с Destroy, некоторые обижаются, что ссылка не очищается, здесь абсолютно также.


 
ketmar ©   (2008-06-02 14:14) [16]

>[12] DiamondShark © (2008-06-02 12:50:00)
>Между прочим, WaitForSingleObject не завершается, если во время ожидания
>закрыть хэндл потока:
>
>var

да и не должен. raise != close

---
All Your Base Are Belong to Us


 
Пробегал2....   (2008-06-02 14:18) [17]

Умищ   (02.06.08 14:03) [14]
От закрытия дескриптора он не перестаёт бать валидным.
WaitForSingleObject завершится, когда связанный с дескриптором поток завершится


я тоже так думал! Нихрена, модифицируем пример DiamondShark:

var
gHandle: THandle;
gLog: TextFile;

function ThreadProc1(Param: Pointer): Integer;
begin
while true do Sleep(0);
Result := 0 ;
end;

function ThreadProc2(Param: Pointer): Integer;
var
rc: DWORD;
begin
rc := WaitForSingleObject(gHandle, INFINITE);
Rewrite(glog);
writeln(gLog, rc);
CloseFile(gLog);
Result := 0 ;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
dummy: Cardinal;
begin
AssignFile(gLog, "d:\log.txt");
gHandle := BeginThread(nil, 0, ThreadProc1, nil, 0, dummy);
BeginThread(nil, 0, ThreadProc2, nil, 0, dummy);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 CloseHandle(gHandle);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
 TerminateThread(gHandle, 0) ;
end;


так вот если нажать Button2 - то запись в лог происходит. А вот если нажать Button1, а потом Button2 - нет!

Что и требовалось доказать. При закрытии хендла связь с объектом ядра теряется, а выхода из WaitForSingleObject не происходит! Причем никогда не произойдет (только если по истечению тайамаута). Мне кажется это неправильно, нелогично.


 
oxffff ©   (2008-06-02 14:25) [18]


> После чего делается CloseHandle, но это не пробуждает поток,
>  выхода из WaitForSingleObject (вот это на мой взгляд странно)
> не происходит. Но при этом "связь" с объектом ядра теряется,
>  и когда объект ядра уничтожен - поток так и остается зависшим
> над WaitForSingleObject... Я честное слово - даже не подозревал!
>


А что возвращает Closehandle?


 
Пробегал2....   (2008-06-02 14:27) [19]

а самое ужасное, что это, видимо, распространяется на все объекты ядра...
Например, если поток висит над хендлом event"а и этому event"у сделать CloseHandle(EventWait), то выхода из WaitForSingleObject опять же не происходит...

Вот тут я вообще задумался. Частенько у меня event"ы разделяют доступ к какому-нибудь ресурсу. И при завершении работы просто делается CloseHandle... Ну допустим я буду делать:

PulseEvent(EventWait);
CloseHandle(EventWait);


но где гарантия что между PulseEvent и CloseHandle другие два потока не выстроятся, то есть один займет EventWait, а другой зависнет по WaitForSingleObject над этим EventWait. И тут из завершающего потока я делаю CloseHandle - тогда второй поток как раз и зависнет навсегда. Ситуацию редкая, это надо чтобы успели два потока зайти... Но тем не менее?!


 
oxffff ©   (2008-06-02 14:29) [20]


> Пробегал2....   (02.06.08 14:27) [19]


SignalObjectAndWait?


 
Пробегал2....   (2008-06-02 14:33) [21]

oxffff ©   (02.06.08 14:25) [18]
А что возвращает Closehandle?


возвращает True


 
Пробегал2....   (2008-06-02 14:36) [22]

oxffff ©   (02.06.08 14:29) [20]
SignalObjectAndWait?


блииин, точно!

Только даже не это, а просто:

WaitForSingleObject(EventWait);
CloseHandle(EventWait);


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


 
Умищ   (2008-06-02 14:39) [23]


> Что и требовалось доказать. При закрытии хендла связь с
> объектом ядра теряется, а выхода из WaitForSingleObject
> не происходит! Причем никогда не произойдет (только если
> по истечению тайамаута). Мне кажется это неправильно, нелогично.
>


А этого и не надо было доказывать.
Если ты собираешься с дескрипторм работать - не закрывай его до окончания использования.
Если выполнишь TermiateThread до CloseHandle, то всё будет так, как и ожидается - функция ожидания прервётся.


 
Пробегал2....   (2008-06-02 15:13) [24]

Умищ   (02.06.08 14:39) [23]
А этого и не надо было доказывать


да ладно? А с чего ты тогда написал абсолютно неправильный пост номер [14]? ;))))

Умищ   (02.06.08 14:39) [23]
Если ты собираешься с дескрипторм работать - не закрывай его до окончания использования


О! Как это гармонично коррелирует с твоей фразой:

От закрытия дескриптора он не перестаёт бать валидным. ;)))

Умищ   (02.06.08 14:39) [23]
Если выполнишь TermiateThread до CloseHandle, то всё будет так, как и ожидается - функция ожидания прервётся


да ты гений ;)


 
Сергей М. ©   (2008-06-02 15:21) [25]


> Пробегал2....   (02.06.08 15:13) [24]


Скажи, а за каким тебе понадобилось FreeOnTerminate=True, если ты все равно ждешь завершения потока ?


 
Умищ   (2008-06-02 15:27) [26]


> От закрытия дескриптора он не перестаёт бать валидным. ;
> )))


Повторю это ещё раз.
И отошлю хотя бы почитать внимательно Рихтера, чтобы можно было хоть немного с немного понимающим человеком разговаривтаь.


 
Умищ   (2008-06-02 15:55) [27]

Ладно, на самом деле при закрытии дскриптора ты теряешь возможность управлять доп. потоком. Тут я неправильно сказал. При закрытии дескриптора сам объект никуда не исчезает.

function ThreadProc1(Param: Pointer): Integer;
begin
 Sleep(3000);
//   while true do Sleep(0);
  Result := 0 ;
end;


 
Пробегал2....   (2008-06-02 17:58) [28]

Сергей М. ©   (02.06.08 15:21) [25]
Скажи, а за каким тебе понадобилось FreeOnTerminate=True, если ты все равно ждешь завершения потока ?


да я в общем разницы не видел. Я не знал, что метод Destroy может так "насолить".

Умищ   (02.06.08 15:27) [26]
отошлю хотя бы почитать внимательно Рихтера


всегда удивлялся, когда люди прочитав Рихтера начинают считать, что теперь все знают лучше других. Прямо слово волшебное стало - "Рихтер...". При его упоминании все оппоненты видимо должны пасть ниц ;)))

Умищ   (02.06.08 15:55) [27]
Тут я неправильно сказал


отлично, что ты умеешь признавать свои ошибки.

Умищ   (02.06.08 15:55) [27]
При закрытии дескриптора сам объект никуда не исчезает.


а разве кто говорил про уничтожение потока, если закрыть описатель на поток? Чтобы поток был уничтожен - надо или вызвать TerminateThread или чтобы поток вызывал процедуру ExitThread.

Но это что касается потоков, но есть и другие объекты ядра. Например, эвенты. Вот для event"ов справедливо, что если закрывают  все описатели на event - объект ядра уничтожается.


 
Пробегал2....   (2008-06-02 18:27) [29]

oxffff ©   (02.06.08 14:29) [20]
SignalObjectAndWait?


Пробегал2....   (02.06.08 14:36) [22]
блииин, точно!

Только даже не это, а просто:

WaitForSingleObject(EventWait);
CloseHandle(EventWait);


а ведь кстати нифига! Я в удаляюшем потоке делаю WaitForSingleObject(EventWait), после чего сделаю Closehandle(EventWait).

Но если между двумя этими процессами какой-то второй поток тоже вызовет WaitForSingleObject(EventWait)? Так как удаляющий поток захватил EventWait, то второй поток зависнет. А после CloseHandle(EventWait) останется висеть навсегда...

Бррр... Так какой же способ ПРАВИЛЬНОГО удаления ресурсов? Получается бред... Нет достоверного способа узнать на момент вызова CloseHandle, что объект ядра на который указывает этот описатель не занят каким-то потоком...


 
Пробегал2....   (2008-06-02 18:39) [30]

И еще интересно, то случайно не относится к COM портам?!

Допустим, я вызываю GetOverlappedResult с последним параметром true, фактически он висит над операцией чтения или записи, то есть над TOverlapped.hEvent. Не будет ли он после этого висеть вечно опять же, если я сделаю CloseHandle над COM-портом?


 
Умищ   (2008-06-02 18:56) [31]


> всегда удивлялся, когда люди прочитав Рихтера начинают считать,
>  что теперь все знают лучше других. Прямо слово волшебное
> стало - "Рихтер...". При его упоминании все оппоненты видимо
> должны пасть ниц ;)))


Ты зря так. Изучив Рихтера, можно получить хотя бы какой-то уровень знаний.


 
Пробегал2....   (2008-06-02 22:23) [32]

Умищ   (02.06.08 18:56) [31]
Ты зря так


а ты зря читаешь каждое пятое слово. Попробуй прочесть пост полностью.


 
Игорь Шевченко ©   (2008-06-02 22:35) [33]

Рихтера читать наизусть.


> всегда удивлялся, когда люди прочитав Рихтера начинают считать,
>  что теперь все знают лучше других


Для этого и основания есть - Рихтер разжевывает настолько, что кашица (пардон, знания) становятся легкоусвояемой, в чем ему, Рихтеру, большой респект.

Кстати, деструктор TThread поток не завершает, а сам ждет его завершения...
Вы там друг друга не жедете ? :)


 
Пробегал2....   (2008-06-03 00:07) [34]

Игорь Шевченко ©   (02.06.08 22:35) [33]
Кстати, деструктор TThread поток не завершает, а сам ждет его завершения...
Вы там друг друга не жедете ? :)


вижу, что тему читали по диагонали.

Как вы, Игорь, знаете при FreeOnTerminate равным true поток вызывает деструктор уже после того, как присвоил:
Thread.FFinished := True;

Соответственно, ничего он не ждет:

if (FThreadID <> 0) and not FFinished then
 begin
   Terminate;
   if FCreateSuspended then
     Resume;
   WaitFor;
 end;


Что очень логично. Если бы поток сам выполнял свой деструктор и в нем ждал заершения самого себя - он бы никогда не дождался.


 
Игорь Шевченко ©   (2008-06-03 00:18) [35]

Пробегал2....   (03.06.08 00:07) [34]


> вижу, что тему читали по диагонали.


А ты пиши прямо - я буду прямо читать. Как пишешь, так и читаю, не взыщи.


> Соответственно, ничего он не ждет:


А метод WaitFor с какой целью вызывается ?


> Если бы поток сам выполнял свой деструктор


Если поток должен выполнить свой деструктор, он устанавливает соответствующее событие (до выполнения деструктора, в процедуре ThreadProc), метод WaitFor ждет не один объект, а два.

Об чем вообще разговор ? У тебя проблема есть ?
Заниматься цветистым анализом кода Borland мне не интересно.


 
Пробегал2....   (2008-06-03 15:55) [36]

Игорь Шевченко ©   (03.06.08 0:18) [35]
А метод WaitFor с какой целью вызывается ?


Пробегал2....   (03.06.08 0:07) [34]
if (FThreadID <> 0) and not FFinished then
begin
  Terminate;
  if FCreateSuspended then
    Resume;
  WaitFor;
end;


Игорь Шевченко ©   (03.06.08 0:18) [35]
Если поток должен выполнить свой деструктор, он устанавливает соответствующее событие


Пробегал2....   (03.06.08 0:07) [34]
уже после того, как присвоил:
Thread.FFinished := True;


Игорь Шевченко ©   (03.06.08 0:18) [35]
Об чем вообще разговор ?


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

Игорь Шевченко ©   (02.06.08 22:35) [33]
Кстати, деструктор TThread поток не завершает, а сам ждет его завершения...


Но я в самом первом своем посте сказал:

Пробегал2....   (02.06.08 4:28)
В Execute методе выставляется FreeOnTerminate в True


поэтому к чему вы это сказали - не знаю. Это ведь не у меня спрашивать надо.

Игорь Шевченко ©   (03.06.08 0:18) [35]
У тебя проблема есть ?


была проблема, но ее решил DiamondShark в посте [12]



Страницы: 1 вся ветка

Текущий архив: 2008.07.20;
Скачать: CL | DM;

Наверх




Память: 0.59 MB
Время: 0.019 c
1-1195486930
dreamse
2007-11-19 18:42
2008.07.20
Как запретить завершать свой приложение?


15-1212423217
Randew
2008-06-02 20:13
2008.07.20
Задачи на комбинаторику


15-1212366526
Пробегал2....
2008-06-02 04:28
2008.07.20
Невероятная работа потока


3-1202764635
alexnmsk
2008-02-12 00:17
2008.07.20
Хранимая процедура


4-1193078322
Wiedzmin
2007-10-22 22:38
2008.07.20
Нажатие кнопки мыши