![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
TThread + Synchronize = bug [D7, WinXP]
abb777 © (2010-12-19 21:25:00)Hi, All,
Не могу понять, в чем дело. Вроде все, как надо, сделал.
Есть несколько одинаковых нитей, взаимодействующих через события (CreateEvent() / WaitForSingleObject() ) друг с другом. В методе Execute вызывается Synchronize() для отображения хода выполнения каждой нити.
Все работает прекрасно, если запустить эту систему нитей и дать ей доработать до конца. В конце все нити уничтожаются, все TThread-ы уничтожаются, все на ура.
Проблема в принудительном останове. Все виснет, если в основной нити VCL просто вызвать нечто типа
for i := 0 to FLayers.Count - 1 do
begin
FLayers.Layer[i].FThread.Terminate;
end;
Данный код просто завешивает все нити. Они висят, как видно в отладочном окне "Threads" на некоем вызове WaitForSingleObject(), но, насколько я понял, это не "мой" WaitForSingleObject() из метода Execute(), а некий другой. Внутри Execute() все с виду ОК - Terminated там анализируется. Да и дело, похоже не в этом. Если я закомментарю вызов Synchronize(), то все останавливается ОК, все нити завершаются, как только я выйду из кода, приведенного выше.
Исходя из этого факта я предположил, что зависание происходит в коде самого TThread, а именно здесь:
class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord);
..............
LeaveCriticalSection(ThreadLock);
try
WaitForSingleObject(SyncProc.Signal, INFINITE); <<<<<<<<<
finally
EnterCriticalSection(ThreadLock);
end;
...................
Перепробовал кучу всяких изворотов, убрал Synchronize() из этих нитей, которых может быть и 100 (сейчас только 10), и создал спец. нить для отображения, а всю информацию для отображения "рабочие" нити валят в список, который разгребает эта "нить отображения". Тот же эффект. Все работает и завершается по окончанию метода Execute() нормально, но при принудительно/досрочном завершении все виснет. Причем виснут все нити, а не только та одна, где сейчас есть вызов Synchronize.
Все операции с общими объектами сделаны через критическую секцию, так что с этим все д.б. в порядке.
Кто-нибудь такое встречал?
Есть идеи?
Заранее спасибо.
Дмитрий Белькевич (2010-12-21 10:40:00) [21]
> .... попробовал :( виснет так же. Зацикливается внутри TThread.
> WaitFor.
Уже прогресс. Ищи, почему у тебя треды не останавливаются (не выходят из Execute).
Ega23 (2010-12-21 10:33:00) [22]
> Тут уже спрашивали выше, зачем я из деструктора нити присваиваю
> nil ссылке на нить у ее родителя.
Вот для этого делаешь PostMessage из деструктора. И в LParam пихаешь Self. А в списке нитей берёшь IndexOf(TMyThread(LParam)) и удаляешь его. Не вызывая деструктор.
abb777 (2010-12-21 09:44:00) [23]За открытие глаз относительно SendMessage отдельное спасибо. Действительно, балбес, не дочитал до конца справку...
Насчет FreeOnTerminate - так я же в критической секции это делал, когда все нити заведомо работали. Это только в последнем варианте я попробовал то, что мне предложили. И анализировал флаг окончания Execute нити, находящийся у ее родителя, прежде чем к ней обращаться.
> и он может в любой момент помахать ручкой
Тут уже спрашивали выше, зачем я из деструктора нити присваиваю nil ссылке на нить у ее родителя. Вот как раз для этого. Этого + крит. секции недостаточно?
И потом, если нить завершилась, а я к ней обращаюсь, то это как минимум приведет к AV, но никак не к зависанию "живой" нити, как я это вижу в реальности. Когда происходит глюк, то ссылки на все нити живые, что и приводит к зацикливанию в CancelIteration. Значит ни один из деструкторов этих нитей не сработал. Что-то концы с концами не сходятся.
han_malign (2010-12-21 08:59:00) [24]
> Я утверждаю, что SendMessage строго говоря вообще ничего
> не "посылает". Она просто вызывает соотв. оконную процедуру,
> и, естественно, выполняет ее, т.е. "дожидается ее завершения".
- но, "строго говоря", с одним маленьким незначительным нюансом:
SendMessage Function
...
Remarks
...
If the specified window was created by the calling thread, the window procedure is called immediately as a subroutine. If the specified window was created by a different thread, the system switches to that threadand calls the appropriate window procedure.Messages sent between threads are processed only when the receiving thread executes message retrieval code. The sending thread is blocked until the receiving thread processes the message. However, the sending thread will process incoming nonqueued messages while waiting for its message to be processed. To prevent this, use SendMessageTimeout with SMTO_BLOCK set. For more information on nonqueued messages, see Nonqueued Messages.
> Мне в этом плане больше SendMessage нравится, в отличие
> от Syncronize, туда ещё и параметры можно положить.
- до D5 - Syncronize работал именно через SendMessage, но затем по каким то причинам перешли на собственную очередь(ну разве что - плюс в том что окно не нужно???)...
> Зацикливается внутри TThread.WaitFor.
вот это FreeOnTerminate := true; - забудь как страшный сон...
Если ты выставил этот флаг - обращаться к экземпляру объекта из другого потока ты не имеешь права(ни с Terminate, ни с WaitFor, ни с чем другим, за исключением контекста синхронного Callback-а(до выхода из которого поток гарантированно не помрет)), и должен вообще забыть что у тебя такая ссылка была, потому как ты его отпустил в вольное плавание, и он может в любой момент помахать ручкой...
Германн (2010-12-21 01:51:00) [25]
> Есессно :) Я выше изложил примерно то же самое, только другими
> словами. Я утверждаю, что SendMessage строго говоря вообще
> ничего не "посылает". Она просто вызывает соотв. оконную
> процедуру, и, естественно, выполняет ее, т.е. "дожидается
> ее завершения". Хотя и не ждет она совсем. Факт ожидания
> отсутствует, как таковой, в отличие от Synchronize(), где
> факт ожидания - в лице пресловутого WaitForSingleObject().
>
>
Бред.
Не надо излагать "другими словами". Нужно прочитать учебник.
Ega23 (2010-12-21 01:45:00) [26]
> Я утверждаю, что SendMessage строго говоря вообще ничего
> не "посылает".
Бляха-муха, я же тебе из хелпа конкретную выдержку привёл!
Не, ты конечно можешь утверждать всё, что угодно. Даже что Земля налетит на Небесную Ось. Про трёх слонов на черепахе, плывущей в бесконечном окияне - тоже можешь утверждать.
Что касается Syncronize.
Рекомендую вообще забыть про класс TThread и написать свой. Тогда многое станет понятным.
abb777 (2010-12-21 01:42:00) [27].... попробовал :( виснет так же. Зацикливается внутри TThread.WaitFor.
abb777 (2010-12-21 01:30:00) [28]> чуешь разницу?
Есессно :) Я выше изложил примерно то же самое, только другими словами. Я утверждаю, что SendMessage строго говоря вообще ничего не "посылает". Она просто вызывает соотв. оконную процедуру, и, естественно, выполняет ее, т.е. "дожидается ее завершения". Хотя и не ждет она совсем. Факт ожидания отсутствует, как таковой, в отличие от Synchronize(), где факт ожидания - в лице пресловутого WaitForSingleObject().
С PostMessage мне все было ясно, кроме одного: не сказано, а что делает та нить, которая разгребает очередь, в то время, когда наша нить постит сообщение. Это я и спрашивал выше. Надо окружать PostMessage крит. секцией или это винда разрулит без меня? Подозреваю, что последнее, но хотелось бы знать точно. А не написано.
В общем сделал я тест. Вроде ничего не поменялось. Жмем старт, дожидаемся, пока цифры сами остановятся, закрываем окно и оно закрывается. А если попробовать нажать стоп почти сразу после старта то курсор-часики. Ну или окно можно попробовать закрыть, что практически то же самое. Тест лежит тут:
http://home.bokovikov.com/etc/delphi/test.rar
> for i := 0 to FLayers.Count - 1 do
> begin
> FLayers.Layer[i].FThread.Terminate;
> FLayers.Layer[i].FThread.WaitFor;
> end;
>
ОК сейчас попробую. В пылу полемики я это пропустил. Извините.
Ega23 (2010-12-21 00:45:00) [29]
> Извините. Я не хотел никого обидеть, но когда на конкретный
> вопрос получаешь философский ответ, становится немного грустно.
Принято. Просто не забывай, что тебе никто ничего не должен. Это твояпроблема, а не кого-то там. И отвечающим на форуме никто не платит денег за их знания/время.
Это так, лирическое отступление.
> SendMessage - это просто процедура, непосредственно выполняющая
> процедуру обработки сообщения. Выполняет она ее немедленно
> и в той же нити, в которой она вызывается, а отнюдь не в
> основной нити процесса. А требуется как раз это последнее.
> Иначе и копья ломать незачем. Можно и просто вызвать требуемый
> метод напрямую, а не через SendMessage. PostMessage же в
> принципе работает по-другому. Она лишь помещает сообщение
> в очередь, которую разгребает основная нить процесса. Поэтому
> PostMessage может заменить Synchronize, но меня смущает
> тот факт, что сообщения иногда пропускаются и не доходят
> до адресата.
The PostMessage function places (posts) a message in the message queue associated with the thread that created the specified window and returns without waitingfor the thread to process the message.
The SendMessage function sends the specified message to a window or windows. It calls the window procedure for the specified window and does not return untilthe window procedure has processed the message.
чуешь разницу?
Это означает, что если кто-то из какого-то потока послал некоему окну сообщение, то в первом случае (post) тот, кто послал - послал его и забыл про него. И ему пофигу, обработано оно или нет, он пошёл дальше.
Во втором случае (send) он не просто послал, а дождался подтверждения того, что сообщение было обработано. В целом это полностью соответствует методу Syncronize, за исключением того, что в Syncronize ты можешь передать метод без параметров, а в SendMessage присутствуют LParam и WParam, в которые ты можешь что-то запихнуть.
Германн (2010-12-21 00:37:00) [30]
> SendMessage - это просто процедура, непосредственно выполняющая
> процедуру обработки сообщения. Выполняет она ее немедленно
> и в той же нити, в которой она вызывается, а отнюдь не в
> основной нити процесса.
Хм.
Дмитрий Белькевич (2010-12-20 23:20:00) [31]То ли я не по-русски пишу, то ли одно из двух :)
"вместо Terminate связка Terminate + WaitFor"
for i := 0 to FLayers.Count - 1 do
begin
FLayers.Layer[i].FThread.Terminate;
FLayers.Layer[i].FThread.WaitFor;
end;
?
abb777 (2010-12-20 22:30:00) [32]> Ну как хочешь. Разбирайся сам.
Извините. Я не хотел никого обидеть, но когда на конкретный вопрос получаешь философский ответ, становится немного грустно.
> Нет, ты не прав. TThread.Syncronize + F1
Да. действительно. Но тогда ранее полученный ответ про то, что Synchronize ждет уже завершенную нить, как-то не укладывается.
>>Мне в этом плане больше SendMessage нравится, в отличие от Syncronize, туда ещё и параметры можно положить.
SendMessage - это просто процедура, непосредственно выполняющая процедуру обработки сообщения. Выполняет она ее немедленно и в той же нити, в которой она вызывается, а отнюдь не в основной нити процесса. А требуется как раз это последнее. Иначе и копья ломать незачем. Можно и просто вызвать требуемый метод напрямую, а не через SendMessage. PostMessage же в принципе работает по-другому. Она лишь помещает сообщение в очередь, которую разгребает основная нить процесса. Поэтому PostMessage может заменить Synchronize, но меня смущает тот факт, что сообщения иногда пропускаются и не доходят до адресата. Synchronize выглядит более надежно.
ОК. попробую сделать тестовый пример...
Ega23 (2010-12-20 19:23:00) [33]
> SendMessage тоже синхронный вызов. Лучше PostMessage
Это если дальше выполнять надо.
Мне в этом плане больше SendMessage нравится, в отличие от Syncronize, туда ещё и параметры можно положить.
Ega23 (2010-12-20 19:22:00) [34]
> Так вроде Synchronize() не тормозит нить. Весь его смысл
> в этом и есть. Он берет указатель на метод и нить идет своим
> путем, а специальная нить Thread_Lock (вроде так) ждет,
> пока наступит очередь основной нити процесса, и выполняет
> этот метод там. В результате как раз и возникает проблема,
> что на момент выполнения Synchronize() в основной нити
> родительская нить Synchronize может уже завершиться. Именно
> в этом случае и возникает завсон. Я не прав?
Нет, ты не прав. TThread.Syncronize + F1
> Слушайте, давайте без флуда. И так уже голова пухнет...
Ну как хочешь. Разбирайся сам.
Leonid Troyanovsky (2010-12-20 18:14:00) [35]
> abb777 © (20.12.10 13:37) [13]
> Я понимаю, и уже давно, все, что Вы, Леонид, хотите сказать
Многие могли бы рассказать про многопоточные приложения.
Но, пока, увы, и обсуждать нечего.
Кроме туманных симптомов.
Да и уровень подготовки слушателей тоже немного смущает.
--
Regards, LVT.
abb777 (2010-12-20 17:07:00) [36]Кстати...
To han_malign:
> > - правильно - не тормозить расчеты ожиданием синхронизации
> > с основным потоком...
>
Так вроде Synchronize() не тормозит нить. Весь его смысл в этом и есть. Он берет указатель на метод и нить идет своим путем, а специальная нить Thread_Lock (вроде так) ждет, пока наступит очередь основной нити процесса, и выполняет этот метод там. В результате как раз и возникает проблема, что на момент выполнения Synchronize() в основной нити родительская нить Synchronize может уже завершиться. Именно в этом случае и возникает завсон. Я не прав?
abb777 (2010-12-20 16:53:00) [37]Чисто для большего понимания:
To han_malign:
> - правильно - не тормозить расчеты ожиданием синхронизации
> с основным потоком...
>
У меня "рабочие" нити не тормозятся выдачей состояния. Тот WaitForSingleObject(), который там есть, нужен совсем для другого, и его наличие принципиально для решения поставленной задачи проекта. У нити, отвечающей за выдачу состояния расчета, стоит приоритет Idle, что практически эквивалентно Вашему предложению, насколько я понимаю.
> 1...........
У меня так, практически, и есть. Учитывая, что расчетные процедуры занимают времени 1000.1 по сравнению с добавлением элемента в список для отображения.
> 2..........
Насколько я понял, это типа того, что прежде, чем обновлять прогресс-индикатор, надо убедиться, что значение процента изменилось, а не тупо вызывать это обновление при каждой итерации цикла. Если да, то я всегда так и делаю.
> 3..........
> 4...........
Извините, но просто не понял. Если можно, на примере объясните, пожалуйста.
> 5............
До этого я точно не дойду. В событии формы, где собственно отображается прогресс стоит только Application.ProcessMessages, иначе ничего не отображается, видимо, потому, что поскольку процессор загружен почти на 100% во время работы всего этого хозяйства. Если это неправильно, то скажите, пожалуйста, как будет правильно.
clickmaker (2010-12-20 16:43:00) [38]> И к тому же, это точно установлено, что PostMessage() is
> threadsafe? И как конкретно надо это оформлять? Надо ли
> этот вызов окружать критической секцией?
что значит threadsafe? поскольку вызов асинхронный, должна быть гарантия, что данные, которые доступны на момент отправки, будут также доступны и в момент получения. т.е. исключаются локальные переменные и глобальные, которые могут быть изменены другими потоками
> куда посылать PostMessage?
окну-получателю. Если окна нет, должен быть поток с циклом выборки сообщений, и тогда PostThreadMessage ему
abb777 (2010-12-20 16:24:00) [39]
> Почему? Всё будет ровно так, как ты напишешь.
Слушайте, давайте без флуда. И так уже голова пухнет...
> >> Там нет Handle, а я не знаю, как ловить формой такие
> сообщения.
> > Ну у твоего объекта-то Handle есть? Есть. Вот ему и шли.
> Если нет - заведи.
Это вообще о чем? Посмотрите на формат вызова PostThreadMessage() и еще раз скажите, так, чтобы было понятно. Насчет "завести хендл" - надо заводить не просто THandle, а оконную процедуру. Это уже совсем другая песня, хотя я буду смотреть в эту сторону, если проблему с Synchronize() решить не удастся.
> > Конкретный пример, вероятно, помог бы.
> Ну я вечером из дома постараюсь пример привести, если силы
> будут.
Вот это уже по-деловому. Спасибо.
> SendMessage тоже синхронный вызов. Лучше PostMessage
Вот и я как раз об этом(см. выше). Поэтому не "Лучше", а "Только". Но куда посылать PostMessage? И к тому же, это точно установлено, что PostMessage() is threadsafe? И как конкретно надо это оформлять? Надо ли этот вызов окружать критической секцией?
clickmaker (2010-12-20 15:57:00) [40]SendMessage тоже синхронный вызов. Лучше PostMessage