Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.225.255.134] |
|
Сообщ.
#1
,
|
|
|
Корректно ли делать вот так?
TThread.Synchronize(Th, Method); Именно поэтому я не стал писать: Th.Synchronize(Method) |
Сообщ.
#2
,
|
|
|
Задача какая стоит?
Указанная форма Synchronize - классовая статическая, применяется (пока) редко Nil использовать можно - главному потоку не всегда требуется знать, кто там синхронизируется. |
Сообщ.
#3
,
|
|
|
Не надо делать Synchronize, вот честно. Лучше делайте PostMessage и избавьтесь от привычки менять UI напрямую из рабочего потока. Ибо Synchronize - это не многопоточность, это ОДНОпоточность. Почитайте, как он работает - смысл тредов убивается напрочь, и владельцы многоядерников высылают автору программы заслуженные лучи поноса.
|
Сообщ.
#4
,
|
|
|
Ok, тогда ещё пара вопросов:
1. Как можно узнать, что TThread (с FreeOnTerminate = True) ещё работает, а не завершился? Если не вешаться на OnTerminate и устанавливать какую-нибудь переменную вне TThread. Вообще, можно ли определить, что объект освобождён? 2. И можно ли сделать так, чтобы переменная TThread обнулялась при завершении работы? Добавлено И правильно ли я понимаю, что если я прерву поток через TerminateThread (потому как метод Terminate ничего, по сути, не делает), то никакого Free не случится, т.к. этот самый Free (как и вызов метода Execute) происходит в том же самом потоке? Да и вообще, насколько это опасно: вызывать TerminateThread (ведь в ThreadProc много чего ещё происходит после Execute)? |
Сообщ.
#5
,
|
|
|
1. Посылать сообщение или выставлять событие. Собственно, OnTerminate решает эту задачу проще всего
>можно ли определить, что объект освобождён? Не нужно этого. 2. нет Спроектируй работу потоков корректно, тогда в абсолютном большинстве случаев не понадобится ничего обнулять и грубо останавливать поток. |
Сообщ.
#6
,
|
|
|
Цитата MBo @ Дело в том, что мне нужно остановить поток, в котором выполняется WinAPI-функция (в частности, MessageBox). Спроектируй работу потоков корректно, тогда в абсолютном большинстве случаев не понадобится ничего обнулять и грубо останавливать поток. |
Сообщ.
#7
,
|
|
|
В дополнительном потоке выполняется работа, требующая блокирующего обращения к пользовательскому интерфейсу??
Это неправильно. |
Сообщ.
#8
,
|
|
|
С чего вдруг это неправильно?
|
Сообщ.
#9
,
|
|
|
Цитата Jin X @ С чего вдруг это неправильно? Потому что потоку лучше делать расчёты "как заказано". Если у каждого потока будет свой UI, то взаимодействие между ними станет гораздо сложнее. Т.е. это по сути это будут Active Objects в стиле языка SmallTalk со товарищи (Хочешь что-то от потока? Положи ему в очередь сообщений команду. Когда именно он её обработает и ответит - предсказать нельзя, всё взаимодействие будет асинхронное в обе стороны. В результате, тривиальный код выливается в здоровенную state machine, которую трудно читать и ещё труднее - добавлять новый функционал. В ряде случаев синтаксис языка облегчает головную боль, типа кодогенераторов async-await в C# или асинхронных блоков в Objective-C, но кардинального облегчения не будет.) Поэтому в роли "заказчика" пусть выступает основной (aka визуальный) поток, и он уже будет подавать команды рабочему TThread, а тот в роли ведомого - репортить свои достижения обратно через PostMessage или ещё как (почитайте про shared memory, mutex, event и прочие механизмы синхронизации потоков). |
Сообщ.
#10
,
|
|
|
Цитата Jin X @ Да и вообще, насколько это опасно: вызывать TerminateThread (ведь в ThreadProc много чего ещё происходит после Execute)? Вызывать TerminateThread вообще опасно (см. ремарки мсдн), поэтому делать это рекомендуется только в исключительных случаях. После TerminateThread не только ThreadProc не выполняется, а вообще никакой user-mode code (а в XP и ниже даже стэк потока не очищается, что приводит к утечке памяти - по умолчанию 1 Мб адресного пространства). Соотв-но и состояния глобальных переменных, которые мог изменить поток, могут зависнуть в "неконсистентном состоянии". А это могут быть не только залоченные крит.секции, приводящие к очевидному дедлоку, но и неочевидные внутренние состояния системных библиотек, если прерывание потока происходит на вызове функции АПИ (о чем упоминается в тех же ремарках). Простейший пример: если ты собираешься убить поток во время показа MessageBox с hWnd <> 0, то после убийства окно hWnd так и останется заблокированным (disabled) и придется вызывать EnableWindow для его разблокировки. Вопрос - зачем это нужно, если можно решить эту проблему "мирными средствами" (например, вызвать Thread.Terminate и закрыть окно диалога). Цитата Jin X @ потому как метод Terminate ничего, по сути, не делает Этот метод делает то, что нужно - указывает потоку, что он должен сам завершиться, чтобы не пришлось прибегать к тяжелой артиллерии и ковровому бомбометанию в виде TerminateThread. Поэтому "корректно спроектированный поток", рассчитанный на длительную обработку (или возможные зависоны) должен периодически и\или в "узловых точках" (например, после показа диалога) проверять флаг Terminated и самостоятельно корректно завершаться при его установке. |
Сообщ.
#11
,
|
|
|
Цитата Jin X @ 1. Как можно узнать, что TThread (с FreeOnTerminate = True) ещё работает, а не завершился? Если не вешаться на OnTerminate и устанавливать какую-нибудь переменную вне TThread. Можно воспользоваться ReturnValue (менять значения придется самому) или Finished (ставится в True после завершения Execute, при повторном запуске не взводится). С FreeOnTerminate осторожней. Это хорошо для потоков "запустил и забыл", если программа гарантированно дождется завершения, но как только начинаешь рассчитывать на досрочное закрытие приложения, все резко становится намного веселее. |
Сообщ.
#12
,
|
|
|
Цитата Jin X @ Ok, тогда ещё пара вопросов: 1. ... 2. ... Если используешь FreeOnTerminate = True, то сохранять ссылку на TThread в переменной не имеет никакого смысла, т.к. при асинхронной работе объект может быть разрушен в любой момент, со всеми вытекающими отсюда прелестями, самой лучшей (но маловероятной) из которых является AV. И обнуление переменной тут ничего не даст, т.к. разрушение объекта может произойти сразу после проверки переменной на nil\Assigned. |
Сообщ.
#13
,
|
|
|
Цитата Mr.Delphist @ Зачем так заморачиваться?Если у каждого потока будет свой UI, то взаимодействие между ними станет гораздо сложнее.......... Речь идёт просто о единичном MessageBox в отдельном потоке, не более того Цитата leo @ Это да т.к. разрушение объекта может произойти сразу после проверки переменной на nil\Assigned В общем, решил TerminateThread заменить на EnumThreadWindows/SendMessage(WM_CLOSE) |
Сообщ.
#14
,
|
|
|
И все же такая архитектура глубоко порочна, кмк. Тут в рассылке ICS недавно один персонаж похожие штуки проворачивал - показывал мессаджбоксы из обработчиков асинхронных сокетов. Тоже знает толк в извращениях...
|
Сообщ.
#15
,
|
|
|
Fr0sT, в чём порочность? Конструктивно только, без пространных рассуждений о силах вселенной
Хорошо, пусть обычно основной поток используется для всяких GUI-штук, связки различных компонентов и основных действий. Дополнительные потоки - для вычислений и пр. сопутствующих радостей. Теперь ситуация: я хочу вывести MessageBox "в фоновом режиме" (немодальный то бишь - не будем тут поднимать вопрос "зачем"... если это кому-то непривычно, то не значит, что это не имеет права на жизнь), но такой "встроенной" возможности нет. Т.е. фактически это либо создание окна и работа с ним через VLC (или то же самое, но через WinAPI), либо вывод MessageBox/MessageBoxTimeout в отдельном потоке (ну не пихать же основной код программы в отдельный поток ради того, чтобы MessageBox выводился из основного - это явный бред). Вот здесь я об это уже писал: Найти и закрыть MessageBox Сейчас решил вернуться к этому и закончить дело. |
Сообщ.
#16
,
|
|
|
Немодальный Message box http://stackoverflow.com/a/23647706
Цитата Take a look at the function CreateMessageDialog in unit Vcl.Dialogs. This returns a the standard VCL MessageDialog form which you can manipulate, if necessary, and then show, modal or normally, as you wish. А порочность вторичного потока с модальностью UI всё та же - надо уметь понимать есть ли у этого потока сейчас модальный диалог на экране и "нажать ему кнопочку", иначе из цикла прокачки сообщений особо и не выйти. А значит - не сделать graceful-завершение (т.е. либо поток остаётся жить вечно и приложение не сможет завершиться, мешая в том числе и нормальной перезагрузке ОС, либо TerminateThread со всеми минусами что описаны выше). Вообще, при всей своей кажущейся тривиальности, UI-поведение - тема очень и очень многогранная с точки зрения удобства пользователя (user experience, UX). Скажем, ранее я тоже считал, что любой диалог должен быть модальным, всё прочее ересь и неудобство. Но когда я посмотрел на Perforce-клиент (так называемый P4), то был удивлён насколько удобной может быть немодальность диалоговых окон при выполнении тех или иных операций: Сабмитишь? А вот можно зайти в список предыдущих коммитов. Бранчуешь или мёржишь? А вот тебе доступ к общему дереву, к консоли сообщений, к чему угодно. В этом свете стандартные модальные диалоги ОС начинают казаться чугунными кандалами, где можно максимум это лишь погреметь при желании цепью |
Сообщ.
#17
,
|
|
|
Цитата Jin X @ в чём порочность? Порочность не в самом MessageBox, а в кривой логике взаимодействия потоков - с одной стороны тебе хочется автоудаления потока через FreeOnTerminate, "не вешаться" на OnTerminate и не "устанавливать какую-нибудь переменную вне TThread", а с другой стороны ты хочешь каким-то странным способом контролировать завершение потока и даже закрывать его MessageBox. Во-первых, не нужно никакого FreeOnTerminate. Лучше иметь ссылку на объект потока, в которой уже есть масса полезных свойств типа Finished, Handle, ThreadID, а также можно добавить свои, например признак того, что поток находится в состоянии показа MessageBox. Раз тебе все равно приходится контролировать состояние потока, то и удалить его объект не составит труда, когда установлен флаг Finished (и GetExitCodeThread не возвращает STILL_ACTIVE). Если поток находится в состоянии показа MessageBox, то вывести его из этого состояния можно простым PostThreadMessage(ThreadID,WM_QUIT,0,0). Если он может делать что-то еще, то можно использовать штатные средства Terminate\Terminated для корректного досрочного завершения. PS: Не только вызовы EnumThreadWindows/SendMessage(WM_CLOSE), но и PostThreadMessage(ThreadID,WM_QUIT,0,0) из главного потока по любому выглядят коряво. Лучше в самом классе потока создать метод типа CloseMessageBox (или ExitMessageLoop), в котором будет вызываться PostThreadMessage |
Сообщ.
#18
,
|
|
|
Камрады хорошо ответили, добавить особо нечего.
Что касается немодальной мессаги - hWnd = 0 и всё |
Сообщ.
#19
,
|
|
|
Не хочу я юзать VCL. Можно на WinAPI сделать, конечно. Но так мне видится проще.
Цитата Mr.Delphist @ Не вижу тут проблем. EnumThreadWindows - сработала, значит есть (к тому же, у меня есть отдельный флаг для этого). Кидаешь WM_CLOSE (даже если промахнёшься на микросекунду, ничего страшного всё равно не произойдёт).А порочность вторичного потока с модальностью UI всё та же - надо уметь понимать есть ли у этого потока сейчас модальный диалог на экране и "нажать ему кнопочку", иначе из цикла прокачки сообщений особо и не выйти. Цитата leo @ Я наоборот хотел, чтобы FreeOnTerminate устанавливал ThreadObj в nil. А суть этого вопроса вообще в том, чтобы случайно не обратиться к Synchronize уже освобождённого объекта. Но т.к. от Synchronize я отказался, то и вопрос отпал.Порочность не в самом MessageBox, а в кривой логике взаимодействия потоков - с одной стороны тебе хочется автоудаления потока через FreeOnTerminate, "не вешаться" на OnTerminate и не "устанавливать какую-нибудь переменную вне TThread", а с другой стороны ты хочешь каким-то странным способом контролировать завершение потока и даже закрывать его MessageBox. А в самом потоке перед завершением устанавливаются флаги (вне TThread) - само собой. Цитата leo @ Постоянно контролировать мне его состояние не надо. Только при определённых условиях (принудительном закрытии MesageBox'а). Поэтому FreeOnTerminate пусть будет. Проблем с обнулением тоже нет, т.к. см. предыдущий коммент.Во-первых, не нужно никакого FreeOnTerminate. Лучше иметь ссылку на объект потока, в которой уже есть масса полезных свойств типа Finished, Handle, ThreadID, а также можно добавить свои, например признак того, что поток находится в состоянии показа MessageBox. Раз тебе все равно приходится контролировать состояние потока, то и удалить его объект не составит труда, когда установлен флаг Finished (и GetExitCodeThread не возвращает STILL_ACTIVE). Если поток находится в состоянии показа MessageBox, то вывести его из этого состояния можно простым PostThreadMessage(ThreadID,WM_QUIT,0,0). Если он может делать что-то еще, то можно использовать штатные средства Terminate\Terminated для корректного досрочного завершения. А вот PostThreadMessage(ThreadID,WM_QUIT,0,0) - это проще, чем EnumThreadWindows И про GetExitCodeThread=STILL_ACTIVE может пригодиться Добавлено Подскажите ещё... есть ли где-то список всех активных объектов моего класса? Или самому такой только создавать нужно? Для graceful-завершения как раз... Добавлено Цитата Jin X @ Единственный минус - поток может завершиться между проверкой флага, что поток ещё работает и PostThreadMessage.Поэтому FreeOnTerminate пусть будет. Но это тоже маловероятно, т.к. здесь у меня всего несколько инструкций, а между завершением TThread.Execute и TThread.Free (по FreeOnTerminate) гораздо больше инструкций. Добавлено Мне надо сейчас проработать места, где поток может внезапно завершиться, когда не надо (между проверкой и действием). Вот думаю TThread.Suspend'ами разбавить (до проверки), т.к. всякие там InterlockedXXXX в данном случае не катят (неудобны). |
Сообщ.
#20
,
|
|
|
Цитата Jin X @ Мне надо сейчас проработать места, где поток может внезапно завершиться, когда не надо (между проверкой и действием). Вот думаю TThread.Suspend'ами разбавить (до проверки) Госпидя-я... Решить проблему "внезапного завершения" можно только синхронными методами. А SuspendThread - та же опасная асинхронная операция прерывания потока в неопределенном \ непредсказуемом состоянии, как и TerminateThread. Ты вообще в msdn и дельфийскую справку заглядываешь? Зачем все эти кулхацкерские выкрутасы, если есть штатный синхронный способ контролировать завершение потока через OnTerminate? Раз сохранил какие-то ссылки на поток, то повесь ему OnTerminate для обнуления этих ссылок - и всё! Или тебе какая-то сектантская религия не позволяет использовать Synchronize ни под каким соусом? Цитата Jin X @ А суть этого вопроса вообще в том, чтобы случайно не обратиться к Synchronize уже освобождённого объекта. Но т.к. от Synchronize я отказался, то и вопрос отпал. Метод Synchronize предназначен для вызова из самого потока TThread, а не из главного. Как поток может вызвать Synchronize, если он уже завершил Execute и "самоликвидировался"?! Вызывать Synchronize из главного потока допустимо, но не имеет смысла, т.к. это эквивалентно прямому вызову переданного метода без какой-либо "синхронизации" с потоком, которому принадлежит метод или который передан в качестве параметра в Synchronize(Th, Method). |
Сообщ.
#21
,
|
|
|
Цитата leo @ Меня не именно завершение потока интересует (неверно выразился... вернее, _уже_ особо не интересует), а конкретные места в коде потока, где он выполняет что-то (устанавливает значения, вызывает метод и пр). Т.е. мне надо, чтобы это сделал либо доп.поток, либо метод, вызываемый из основного потока, а не оба сразу. А чем Suspend-то опасен?Госпидя-я... Решить проблему "внезапного завершения" можно только синхронными методами. А SuspendThread - та же опасная асинхронная операция прерывания потока в неопределенном \ непредсказуемом состоянии, как и TerminateThread. Ты вообще в msdn и дельфийскую справку заглядываешь? Зачем все эти кулхацкерские выкрутасы, если есть штатный синхронный способ контролировать завершение потока через OnTerminate? Раз сохранил какие-то ссылки на поток, то повесь ему OnTerminate для обнуления этих ссылок - и всё! Или тебе какая-то сектантская религия не позволяет использовать Synchronize ни под каким соусом? p.s. А вообще, без а-ля InterlockedXXXX проблематично это сделать. Думал двойной установкой переменных обойтись, но уж не буду дурью маяться (ошибиться легко, а смысла особого нет) p.p.s. Synchronize и OnTerminate (который тоже вызывается через Synchronize) мне не нравятся тем, что прога может быть, например, консольной, и тогда этого OnTerminate мы не дождёмся вообще никогда. Цитата leo @ Это понятно. Я хотел вставить этот кусок в метод, который может вызываться как из доп. потока, так и из главного. Метод Synchronize предназначен для вызова из самого потока TThread, а не из главного. Как поток может вызвать Synchronize, если он уже завершил Execute и "самоликвидировался"?! Вызывать Synchronize из главного потока допустимо, но не имеет смысла, т.к. это эквивалентно прямому вызову переданного метода без какой-либо "синхронизации" с потоком, которому принадлежит метод или который передан в качестве параметра в Synchronize(Th, Method). Добавлено Кстати, Synchronize ждёт завершения вызова метода (или просто ставит в очередь)? |
Сообщ.
#22
,
|
|
|
Цитата Jin X @ Кстати, Synchronize ждёт завершения вызова метода (или просто ставит в очередь)? Разумеется, ждет. Иначе это был бы не Synchronize, а что-то другое Цитата Jin X @ А чем Suspend-то опасен? Тем, что он прерывает выполнение потока в неопределенном состоянии, например, при захвате им критической секции. А крит.секции, как суслики - ты их не видишь, а они есть - сидят в потайных норах и дельфийского кода (например, в менеджере памяти), и WinAPI. Вообще, SuspendThread это отладочная функция, которая предназначена для вызова из другого процесса, а не своего собственного. Цитата Jin X @ Synchronize и OnTerminate (который тоже вызывается через Synchronize) мне не нравятся тем, что прога может быть, например, консольной, и тогда этого OnTerminate мы не дождёмся вообще никогда. Час от часу не легче. А вдруг немцы? (С) Synchronize в доп.потоке должна работать в паре с CheckSynchronize в главном потоке. В GUI-приложении вызов CheckSynchronize встроен в цикл обработки Application.Run (точнее в Application.Idle). В консольной проге встроенного механизма нет, поэтому нужно либо вызывать CheckSynchronize периодически, либо там, где нужно по логике работы проги, либо использовать другие методы синхронизации потоков. Цитата Jin X @ Постоянно контролировать мне его состояние не надо. Только при определённых условиях (принудительном закрытии MesageBox'а). Какая разница - постоянно или однократно? Как ты узнаешь, что нужно закрыть MessageBox? Тебе сам поток как-то об этом сигнализирует (или разгневаннаый юзер) или ты по своей инициативе проверяешь наличие этого бокса? Если ты по любому хоть раз, как-то проверяешь состояние потока, то почему-бы его и не удалять из памяти при этой проверке? А если поток завершится раньше, и объект TThread повисит какое-то время памяти, то что в этом страшного? |
Сообщ.
#23
,
|
|
|
Цитата leo @ Так, прервал Suspend выполнение потока в неопределённом состоянии, пусть даже при захвате критической секции... иии???Тем, что он прерывает выполнение потока в неопределенном состоянии, например, при захвате им критической секции. А крит.секции, как суслики - ты их не видишь, а они есть - сидят в потайных норах и дельфийского кода (например, в менеджере памяти), и WinAPI. Вообще, SuspendThread это отладочная функция, которая предназначена для вызова из другого процесса, а не своего собственного. Что плохого-то в этом? Если я вскоре вызову ResumeThread ? Цитата leo @ Ну начинается... Т.е. я, делая модуль, реализующий какой-то функционал в фоновом режиме, должен написать: "Вызывайте CheckSynchronize почаще, пожалуйста, а то могут возникать глюки"? Зачем, если я могу всё это сделать без Synchronize? Synchronize в доп.потоке должна работать в паре с CheckSynchronize в главном потоке. В GUI-приложении вызов CheckSynchronize встроен в цикл обработки Application.Run (точнее в Application.Idle). В консольной проге встроенного механизма нет, поэтому нужно либо вызывать CheckSynchronize периодически, либо там, где нужно по логике работы проги, либо использовать другие методы синхронизации потоков. Цитата leo @ А зачем мне его закрывать? Есть MessageBoxTimeout, который сам закроет окно после определённого таймаута. И есть метод Close, который можно вызвать из программы и закрыть это окно.Какая разница - постоянно или однократно? Как ты узнаешь, что нужно закрыть MessageBox? Тебе сам поток как-то об этом сигнализирует (или разгневаннаый юзер) или ты по своей инициативе проверяешь наличие этого бокса? Цитата leo @ А какой в этом смысл? Зачем мне его удалять из памяти при "этой" проверке? И зачем потоку висеть в памяти какое-то время? Если можно просто сделать FreeOnTerminate := True, а перед завершением потока установить флаг во внешней переменной? Тем более, что устанавливать флаг всё равно нужно, т.к. при всё той же "этой" проверке мне надо как-то узнать, что поток ещё не удалён.Если ты по любому хоть раз, как-то проверяешь состояние потока, то почему-бы его и не удалять из памяти при этой проверке? А если поток завершится раньше, и объект TThread повисит какое-то время памяти, то что в этом страшного? Для чего это нужно? Если можно сделать проще Добавлено leo, разговор уже ушёл далеко от первоначальной темы И мне даже кажется, что мы уже немного говорим не о том. Я как доделаю эту штуку (руки до неё всё никак не дойдут), выложу и можно будет подискутировать где какие косяки Может, кому-то модуль и пригодится... Добавлено Собственно, я сейчас уже всё доделал, осталось только оттестить и отладить. Но это уже не сегодня |
Сообщ.
#24
,
|
|
|
Цитата Jin X @ Что плохого-то в этом? Если я вскоре вызову ResumeThread ? в том, что если ты засуспензил поток, в в момент, когда менеджер памяти залочен в крит секции, то ты ты сам не можешь использовать менеждер памяти. а менеджер памяти используется явно и не явно везде. ты разве что сложить пару чисел можешь без него, не меньше. |
Сообщ.
#25
,
|
|
|
В данном случае я собираюсь (собирался... уже по-человечески, через InterlockedComparExchgane сделал) Suspend'ить доп. поток для проверки переменных, т.е. чтобы "сложить" (сравнить) пару чисел. Мне менеджер памяти не нужен был.
|
Сообщ.
#26
,
|
|
|
Если уж так надо суспендить поток, то делай через виндовые средства (эвенты, семафоры и т.д.) и WaitFor*. Ну или по-топорному, while ThreadPaused do Sleep(100). Это хотя бы безопасно.
А вообще чем дальше, тем меньше я понимаю, чего ты хочешь достичь. Неверная архитектура задачи видится мне. |
Сообщ.
#27
,
|
|
|
Цитата Jin X @ leo, разговор уже ушёл далеко от первоначальной темы. И мне даже кажется, что мы уже немного говорим не о том. Во-первых, первоначальная тема (также как и упомянутая "Найти и закрыть MessageBox"), все больше напоминает сюжет "Саги об X, Y и Z", когда вместо вопроса по сути решаемой задачи, задаются и обсуждаются вопросы, которые не имеют к данной задаче никакого отношения или решают ее, мягко говоря, не лучшим, а то и извращенным способом. А о сути и нюансах самой задачи мы узнаем по крупицам в ходе обсуждения (например, тема начиналась с вопроса о Synchronize, а затем вдруг всплыло консольное приложение, в котором Synchronize без.доп телодвижений работать не может). Мы говорим как раз "о том", но ты упорно придерживаешься выбранного ранее пути решения и приводишь в его оправдание совершенно нелогичные доводы. Тебе сразу сказали, что FreeOnTermiate имеет смысл использовать только в случаях, когда 1) тебя совершенно не интересует "судьба" потока - как говорится, запустил и забыл, 2) используется стандартный способ контроля завершения потока через событие OnTerminate (или через свои примочки типа Post\SendMessage). В остальных случаях, когда возникает желание или необходимость контролировать завершение потока не через OnTerminate, задавать FreeOnTerminate = true, не имеет смысла, т.к. это не упрощает, а лишь усложняет логику взаимодействия. И все твои примочки и блуждания в трех соснах (от Synchronize до Terminate\SuspendTrhead и InterlockedXXX) это лишь подтверждают. Ты в предыдущем посте несколько раз повторил "а зачем?", "проще" и т.п. Нет, не проще. Встречный вопрос: зачем "установить флаг во внешней переменной" "... всё равно нужно", если всё, что нужно уже есть в самой переменной TThread? Разве использовать несколько доп.переменных и самому их устанавливать проще, чем использовать готовый объект, в котором все устанавливается автоматически? Зачем доверять потоку удалять свой объект по FreeOnTerminate? Чтобы потом чесать репу - а удалился ли он уже, или находится в состоянии удаления, и не удалится ли на следующей строчке после проверки какого-то там флага, и городить ради этого огород с SuspendTread, шаманить с Interlocked-функциями и т.п.? Зачем все это? Не проще ли самому удалить объект потока, когда он завершился и больше не нужен (хоть в FormDestroy или в завершающем finally консольной проги)? Или ты считаешь, что любую мало-мальскую память нужно освобождать сразу после ее использования? Типа создал строку, передал ее куда-то - непременно вызови S:='', чтобы она не висела в памяти в ожидании автоочистки? Добавлено Цитата Jin X @ А если этот менеджер памяти мне приспичило использовать в тот момент, когда доп. поток залочит его в критической секции (без Suspend'а), то я смогу его использовать? Он будет ждать когда доп. поток его освободит что ли? Разумеется. На то она и крит.секция, чтобы разрешать выполнять некоторый кусок кода только одному потоку. Поэтому, если ты остановишь поток в крит.секции менеджера памяти, то любое неявное выделение\освобождение памяти между Suspend и Resume (например, при операциях со строками) приведет к зависону. Ты конечно можешь этого избежать, всё "оттестить и отладить", но по любому SuspendTread - это специальная функция, использовать которую в обычных\нормальных приложениях не рекомендуется. Поэтому, юзать ее ты можешь только "на собственный страх и риск для домашнего использования". |
Сообщ.
#28
,
|
|
|
leo, всё просто. Обсуждается тема unit'а по созданию немодального MessageBox, который висит в фоне и закрывается либо по таймауту, либо юзером, либо методом Close. Он может работать в консольном приложении, а не только в GUI. Вот и всё.
Цитата leo @ Я, честно говоря, вообще не понимаю, как ты предлагаешь организовать весь процесс. Смотри, логика проста до безобразия.Ты в предыдущем посте несколько раз повторил "а зачем?", "проще" и т.п. Нет, не проще. Встречный вопрос: зачем "установить флаг во внешней переменной" "... всё равно нужно", если всё, что нужно уже есть в самой переменной TThread? Разве использовать несколько доп.переменных и самому их устанавливать проще, чем использовать готовый объект, в котором все устанавливается автоматически? Зачем доверять потоку удалять свой объект по FreeOnTerminate? Чтобы потом чесать репу - а удалился ли он уже, или находится в состоянии удаления, и не удалится ли на следующей строчке после проверки какого-то там флага, и городить ради этого огород с SuspendTread, шаманить с Interlocked-функциями и т.п.? Зачем все это? Не проще ли самому удалить объект потока, когда он завершился и больше не нужен (хоть в FormDestroy или в завершающем finally консольной проги)? Есть метод Show, который создаёт поток (с FreeOnTerminate) и устанавливает переменную (в классе) Status в значение Displaying - таким образом и сам класс и использующий его код будет знать, что окно отображается. Поток создаёт сообщение MessageBoxTimeout, после завершения которого (и непосредственно перед выходом) устанавливает Status в другое значение (Timedout, Button или Breaked). Таким образом я (класс) знаю, есть ли работающий поток, а программа (вызывающая класс) знает, отображается ли сообщение в данный момент. Метод Close (принудительное закрытие) проверяет - Displaying ли сейчас окно? Если да, то посылает ему PostThreadMessage(WM_QUIT). Это всё. Вопрос: зачем мне нужно сделать FreeOnTerminate := False и самому его освобождать? Единственным доводом в этом пользу может служить разве что "вдруг поток освободиться в Close между проверкой Displaying и PostThreadMessage, и тогда я обращусь к несуществующему Thread.Handle (при вызове PostThreadMessage)?" Но и тут есть простое решение: в этом случае поток может сделать сам себе FreeOnTerminate := False, а Close сделать Thread.Free. Это единственный момент, когда это может быть оправдано, на мой взгляд. Если ты другого мнения, пожалуйста, расскажи. Потому что пока я не понимаю смысла делать иначе . Да, можно делать Free именно в Show, но чем это лучше? Цитата leo @ Вот это тут вообще ни причём. Другое дело, что если есть выбор (и это не усложняет код и не делает его менее надёжным), лучше освободить, не так ли? Или ты считаешь, что любую мало-мальскую память нужно освобождать сразу после ее использования? Типа создал строку, передал ее куда-то - непременно вызови S:='', чтобы она не висела в памяти в ожидании автоочистки? Добавлено Цитата leo @ Блуждание произошло из-за того, что я вместо Interlocked зачем-то решил использовать двойные флаги и/или Suspend, чем сейчас и сам удивляюсь.В остальных случаях, когда возникает желание или необходимость контролировать завершение потока не через OnTerminate, задавать FreeOnTerminate = true, не имеет смысла, т.к. это не упрощает, а лишь усложняет логику взаимодействия. И все твои примочки и блуждания в трех соснах (от Synchronize до Terminate\SuspendTrhead и InterlockedXXX) это лишь подтверждают. А Synchronize нужен был для оповещения основной программы о том, что "произошёл запуск" или "окно закрылось". Типа OnXXXX и иже с ними. Сначала хотел сделать 2 варианта: прямой вызов и через Synchronize, потом решил сделать только вызов напрямую, а программист уже сам решит: нужен ему Synchronize или нет и вызовет его. |
Сообщ.
#29
,
|
|
|
Вот такой вопрос у меня ещё: если я выполняю какую-то операцию (например, PostThreadMessage) с потоком, который только что завершил свою работу, ничего страшного, как я понимаю, не произойдёт.
Но! Может ли возникнуть ситуация, что создастся поток с таким же Id и я закрою что-то другое? Каким образом эти Id генерируются? |
Сообщ.
#30
,
|
|
|
Цитата Jin X @ Обсуждается тема unit'а по созданию немодального MessageBox, который висит в фоне и закрывается либо по таймауту, либо юзером, либо методом Close. Он может работать в консольном приложении, а не только в GUI. Вот и всё. Тьфу ты блин, сразу сказать не судьба была? https://github.com/Fr0sT-Brutal/Delphi_DlgCountdown |
Сообщ.
#31
,
|
|
|
Цитата Jin X @ Единственным доводом в этом пользу может служить разве что "вдруг поток освободиться в Close между проверкой Displaying и PostThreadMessage, и тогда я обращусь к несуществующему Thread.Handle (при вызове PostThreadMessage)?" Но и тут есть простое решение: в этом случае поток может сделать сам себе FreeOnTerminate := False, а Close сделать Thread.Free. Простое решение - это использование критической секции (или ее самописного аналога). В рабочем потоке заключаем в крит.секцию изменение статуса Displaying после показа MessageBox, а в методе Close - проверку статуса и посылку сообщения PostThreadMessage. В этом случае все "вдруг" исключаются и можно использовать FreeOnTerminate:=true. Цитата Jin X @ Может ли возникнуть ситуация, что создастся поток с таким же Id и я закрою что-то другое? Каким образом эти Id генерируются? Подробности мне не известны, но думаю, что на коротких интервалах переназначение ID невозможно. Ведь по любому между получением ID процесса или потока и его открытием проходит какое-то время, за которое этот процесс или поток может успеть закрыться. Поэтому в ОС должен использоваться некий механизм назначения ID, исключающий их повтор на коротких интервалах времени. PS: Однако, если есть возможность перестраховаться (например, с использованием крит.секции), то лучше это сделать, "кабы чего не вышло" |
Сообщ.
#32
,
|
|
|
Цитата leo @ К Diaplaying всё равно никто доступа не имеет, поэтому можно и не заключать Простое решение - это использование критической секции (или ее самописного аналога). В рабочем потоке заключаем в крит.секцию изменение статуса Displaying после показа MessageBox, а в методе Close - проверку статуса и посылку сообщения PostThreadMessage. В этом случае все "вдруг" исключаются и можно использовать FreeOnTerminate:=true. Но я всё равно убрал FreeOnTerminate, оказалось, что всё-таки так удобнее делать некоторые вещи . Не получается "забыть" после того, как запустил. Так что спасибо за настойчивость Цитата leo @ У меня есть такой блок:PS: Однако, если есть возможность перестраховаться (например, с использованием крит.секции), то лучше это сделать, "кабы чего не вышло" function TMsgBoxTimeoutNotify.Close(Res: TMsgBoxTimeoutResult = MBTR_BREAKED): Boolean; begin Result := True; if Res = MBTR_DISPLAYING then Res := MBTR_BREAKED; if InterlockedExchange(Integer(FResult), Integer(Res)) = Integer(MBTR_DISPLAYING) then PostThreadMessage(FThread.ThreadID, WM_QUIT, 0, 0); if (FThread <> nil) and (FThread.Handle <> 0) then // Даже если метод Close вызван уже после установки результата потоком, всё равно... begin Result := (WaitForSingleObject(FThread.Handle, FCloseTimeout) <> WAIT_TIMEOUT); // ...ждём реального закрытия окна (обработки PostMessage). DoNotify(mbtOnClose); if not Result then begin FThread.FreeOnTerminate := True; // Если не дождались, отправляем его в свободное плавание. FThread := nil end end end; Благо TThread не обнуляет Handle и ThreadID при завершении работы потока. Но это уже придирки, конечно, т.к. я полагаю, что иначе добрая половина существующих программ, работающих с WaitForSingleObject и иже с ним, были бы "условно ненадёжными" Добавлено А вот так сделан вывод сообщения: function TMsgBoxTimeoutNotify.ShowEx(hWnd: THandle; const Text, Title: String; Flags, Timeout: DWord; Ntf: TMsgBoxTimeoutNotifyRec; const Id: String = ''; LangId: Word = 0): Boolean; begin Result := ReadyToShow(Flags); if Result then begin FreeAndNil(FThread); FResult := MBTR_DISPLAYING; FParams.hWnd := hWnd; FParams.Text := Text; FParams.Title := Title; FParams.Flags := Flags and not MBTF_FLAGS_MASK; FParams.Timeout := Timeout; FParams.LangId := LangId; FId := Id; if FId = '' then FId := GenerateId; SetNotify(Ntf); // От греха подальше пусть будет SetNotify, а не прямое присвоение :) FThread := TMsgBoxTimeoutNotifyThread.Create(True); with TMsgBoxTimeoutNotifyThread(FThread) do begin MsgBox := Self; DoNotify(mbtOnShow); Resume end end end; procedure TMsgBoxTimeoutNotify.SetNotify(Ntf: TMsgBoxTimeoutNotifyRec); begin while InterlockedExchange(NotifyDenyToChange, 1) = 1 do Sleep(0); FNotify := Ntf; NotifyDenyToChange := 0; end; procedure TMsgBoxTimeoutNotifyThread.Execute; var Res: TMsgBoxTimeoutResult; begin with MsgBox do with FParams do begin Res := MessageBoxTimeout(hWnd, PChar(Text), PChar(Title), Flags, LangId, Timeout); if not FreeOnTerminate then // Поток не в свободном плавании (см. метод Close)? if InterlockedCompareExchange(Pointer(FResult), Pointer(Res), Pointer(MBTR_DISPLAYING)) = Pointer(MBTR_DISPLAYING) then // Реузльтат будет другим (не MBTR_DISPLAYING), если окно закрыто методом Close DoNotify(mbtOnClose) end end; procedure TMsgBoxTimeout.BeforeDestruction; begin Close; FThread.Free end; Весь код пришлю позже, отладить всё надо |
Сообщ.
#33
,
|
|
|
С простыми переменными < размера регистра нет смысла юзать interlocked, чтобы проверить их значение. Чтение-запись в них и так атомарна.
|
Сообщ.
#34
,
|
|
|
Fr0sT, все атомарные (locked) операции и так проводятся над данными размером с регистр (а для 64 и 128 бит можно юзать MMX и SSE).
Внутри процессора производятся такие же операции, как и в обычной программе. Меняем EAX и память [M32]: Temp=EAX; EAX=[M32]; [M32]=Temp. И вот где-то по пути в работу может вмешаться другое ядро, тогда получится: Temp1=EAX1 // Ядро1 Temp2=EAX2 // Ядро2 EAX1=[M32] // Ядро1 EAX2=[M32] // Ядро2 [M32]=Temp1 // Ядро1 [M32]=Temp2 // Ядро2 На примере: Если: EAX1=10 EAX2=20 [M32]=30 Тогда: Temp1=EAX1 // Temp1=10 Temp2=EAX2 // Temp2=20 EAX1=[M32] // EAX1=30 EAX2=[M32] // EAX2=30 [M32]=Temp1 // [M32]=10 [M32]=Temp2 // [M32]=20 В итоге получили: EAX1=30 EAX2=30 [M32]=20 А если это делать по очереди (с locked'ами), то будет: Ядро1: Temp1=EAX1 // Temp1=10 EAX1=[M32] // EAX1=30 [M32]=Temp1 // [M32]=10 Ядро2: Temp2=EAX2 // Temp2=20 EAX2=[M32] // EAX2=10 [M32]=Temp2 // [M32]=20 В итоге получили: EAX1=30 EAX2=10 [M32]=20 Аналогично с увеличением на 1: EAX=[M32]; EAX++; [M32]=EAX. Пусть: [M32]=10 Тогда: EAX1=[M32] // EAX1=10 EAX2=[M32] // EAX2=10 EAX1++ // EAX1=11 EAX2++ // EAX2=11 [M32]=EAX1 // [M32]=11 [M32]=EAX2 // [M32]=11 (на самом деле там используется xadd, который немного по-другому работает, но для иллюстрации такой пример сгодится). Да и просто, пойдём от простой логики: если бы это было не нужно, не было бы функций InterlockedCompare, InterlockedIncrement и пр. |
Сообщ.
#35
,
|
|
|
Я не говорю про обмен - на уровне Паскаля это уже не одна операция. Я говорю о чтении или записи. Тогда (чтение тривиально, рассмотрим запись):
[M_src1]=10 [M_src2]=20 [M32]=30 Тогда: EAX1=[M_src1] // EAX1=30 EAX2=[M_src2] // EAX2=20 [M32]=EAX1 // [M32]=10 [M32]=EAX2 // [M32]=20 В итоге получили: EAX1=10 EAX2=20 [M32]=30 Добавлено Цитата Jin X @ все атомарные (locked) операции и так проводятся над данными размером с регистр (а для 64 и 128 бит можно юзать MMX и SSE) Это к чему? Я знаю. Добавлено Цитата Jin X @ Да и просто, пойдём от простой логики: если бы это было не нужно, не было бы функций InterlockedCompare, InterlockedIncrement и пр. С InterlockedIncrement очевидно (это опять же 2 операции), а вот InterlockedCompare я не очень понимаю зачем нужно. Разве что сразу получить прежнее значение. |
Сообщ.
#36
,
|
|
|
Цитата Fr0sT @ С точки зрения ASM-кода это как раз 1 операция INC [M32].С InterlockedIncrement очевидно (это опять же 2 операции) Цитата Fr0sT @ С точки зрения паскаля это тоже 1 операция, он так же генерит XCHG [M32],EAX.Я не говорю про обмен - на уровне Паскаля это уже не одна операция. Цитата Fr0sT @ Да, как минимум для этого. Как уже сказал, это тоже одна операция XCHG [M32],EAX.InterlockedCompare я не очень понимаю зачем нужно. Разве что сразу получить прежнее значение. InterlockedCompareExchange = CMPXCHG [M32],EAX. Там везде 1 операция. Вопрос не в количестве инструкций ассемблера, а в кол-ве операций ВНУТРИ процессора. Просто чтение и просто запись (A := 10; он же MOV [M32],EAX) - согласен, смысла нет (и то, это просто логическое предположение, х/з как там внутри устроено). Но где идёт изменение хранящегося внутри значения, обмен (со сравнением или без) - там да. Добавлено Цитата Fr0sT @ Это к тому, что ты пишешь:Это к чему? Я знаю. Цитата Fr0sT @ С простыми переменными < размера регистра нет смысла юзать interlocked, чтобы проверить их значение. Чтение-запись в них и так атомарна. Добавлено Я даже больше скажу: ДВЕ операции (не внутри процессора, а в количестве ассемблерных инструкций) ты залочить никак не сможешь. Ну разве что сделать spinlock, но это уже псевдо-лок. |
Сообщ.
#37
,
|
|
|
Цитата Jin X @ С точки зрения ASM-кода это как раз 1 операция INC [M32]. Если с возвратом старого значения - две. Если старое не нужно - то одна, и тогда и InterlockedIncrement не особо нужен. Цитата Jin X @ С точки зрения паскаля это тоже 1 операция, он так же генерит XCHG [M32],EAX. Попробуй сгенерить кодом на Паскале такой asm я имею в виду, без всяких функций. Компилятор не настолько умный, к сожалению. А стало быть, это целых три операции. Цитата Jin X @ Вопрос не в количестве инструкций ассемблера, а в кол-ве операций ВНУТРИ процессора. Я не большой знаток устройства процов, но мне кажется, что инструкция, хотя бы снаружи - вне зависимости от того, как реализовано унутрях - атомарна. Иначе любому менеджеру потоков/процессов пришлось бы лочить каждую инструкцию, чтобы не дай бог не запустить другой поток, пока выполняется инструкция прежнего. Цитата Jin X @ Это к тому, что ты пишешь: Все равно не понял, зачем эта фраза, ну да ладно. |
Сообщ.
#38
,
|
|
|
Цитата Fr0sT @ Зачем что-то утверждать, если не знаешь точно? Если с возвратом старого значения - две. Если старое не нужно - то одна, и тогда и InterlockedIncrement не особо нужен. InterlockedIncrement выполняет 1 операцию (инструкцию CPU): xadd !!! Которая прибавляет некоторое значение, одновременно считывая старое. Смотри картинку... Прикреплённый файл2016_09_16_15_30_29_Unit1.pas.png (13,35 Кбайт, скачиваний: 422) Добавлено Повторюсь: Цитата Jin X @ Все Interlocked-функции работают с 1 операцией! За исключением inline-функций C++, которые организуют spinlock типаДВЕ операции (не внутри процессора, а в количестве ассемблерных инструкций) ты залочить никак не сможешь. Ну разве что сделать spinlock, но это уже псевдо-лок. FORCEINLINE LONGLONG _InlineInterlockedExchange64 ( _Inout_ _Interlocked_operand_ LONGLONG volatile *Target, _In_ LONGLONG Value ) { LONGLONG Old; do { Old = *Target; } while (InterlockedCompareExchange64(Target, Value, Old) != Old); return Old; } #define InterlockedExchange64 _InlineInterlockedExchange64 FORCEINLINE LONGLONG _InlineInterlockedExchangeAdd64 ( _Inout_ _Interlocked_operand_ LONGLONG volatile *Addend, _In_ LONGLONG Value ) { LONGLONG Old; do { Old = *Addend; } while (InterlockedCompareExchange64(Addend, Old + Value, Old) != Old); return Old; } #define InterlockedExchangeAdd64 _InlineInterlockedExchangeAdd64 |
Сообщ.
#39
,
|
|
|
Бррр. Мы о разном говорим. Я - о том, что без interlocked функций определенные действия на Паскале будут транслироваться в 2 и более инструкций (обмен, сравнение-и-присвоение, увеличение-и-возврат-старого и т.д.). Которые, разумеется, небезопасны с т.з. мультитредовости. Однако если операция на Паскале транслируется в одну инструкцию (напр., Inc без получения старого значения), то необходимости в interlocked нет.
|
Сообщ.
#40
,
|
|
|
Цитата Fr0sT @ А, вон ты о чём!Бррр. Мы о разном говорим. Я - о том, что без interlocked функций определенные действия на Паскале будут транслироваться в 2 и более инструкций (обмен, сравнение-и-присвоение, увеличение-и-возврат-старого и т.д.). Которые, разумеется, небезопасны с т.з. мультитредовости. Однако если операция на Паскале транслируется в одну инструкцию (напр., Inc без получения старого значения), то необходимости в interlocked нет. Но тут ты тоже не прав . Посмотри ещё раз скриншот - там перед xadd стоит lock. Не просто так. Как я уже говорил, второе ядро может вклиниться в работу во время (внутренних!!!) вычислений первого и будет как в моём сообщении #34, где оба одновременно пытаются изменить значение, а оно меняется только на 1 вместо 2. Я накатал "по бырому" демку сего процесса. Вот основной код (см. TMyThread.Execute): unit Main; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ComCtrls; type TForm1 = class(TForm) Button1: TButton; Edit1: TEdit; Label1: TLabel; CheckBox1: TCheckBox; TrackBar1: TTrackBar; Label2: TLabel; Button2: TButton; Edit2: TEdit; procedure Button1Click(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure CheckBox1Click(Sender: TObject); procedure TrackBar1Change(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } end; TMyThread = class(TThread) N: Integer; procedure Execute; override; end; var Form1: TForm1; A, B: TMyThread; X: Integer; D: Integer = 0; Run: Boolean = False; UseLock: Boolean = False; implementation {$R *.dfm} procedure TMyThread.Execute; begin N := 0; repeat asm mov eax,Self cmp UseLock,0 je @NoLock lock inc [X] inc [eax+N] jmp @End @NoLock: inc [X] inc [eax+N] @End: end; Sleep(D) until Terminated end; procedure TForm1.Button1Click(Sender: TObject); begin if Run then begin A.Terminate; B.Terminate; Exit end; Run := True; X := 0; A := TMyThread.Create(False); B := TMyThread.Create(False); repeat Edit1.Text := IntToStr((A.N+B.N)-X); Application.ProcessMessages; Sleep(1) until A.Terminated; A.Free; B.Free; Run := False end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin if Run then Button1.Click end; procedure TForm1.CheckBox1Click(Sender: TObject); begin UseLock := CheckBox1.Checked end; procedure TForm1.TrackBar1Change(Sender: TObject); begin D := TrackBar1.Position; Edit2.Text := IntToStr(D) end; procedure TForm1.Button2Click(Sender: TObject); begin X := 0; A.N := 0; B.N := 0 end; end. Суть в том, что два потока увеличивают внешнюю переменную X на 1 (с lock'ом и без lock'а) ассемблером (т.е. это однозначно единая операция) и внутреннюю переменную на 1 (каждый свою A.N и B.N). И в процессе их работы мы постоянно выводим на экран "ошибку" (A.N+B.N) - X. Если галочки "LOCK" нет, это число постоянно увеличивается. Если есть, то рост "ошибки" останавливается. Угадай почему? Если двигать ползунок Sleep (который будет увеличивать задержку между изменением значений в миллисекундах) вправо, то скорость роста "ошибки" при выключенном "LOCK" замедляется, что говорит о том, что в нормальных условиях таких совпадений не возникает (на больших значениях ждать приходится очень долго), но хороший программист всё же должен учитывать такую возможность (даже если она 1 на триллион или меньше, потому что, как говорят, "на грех и грабли стреляют") При Sleep=0 и галочке "LOCK" показатель дёргается - это нормально, т.к. чтение значений A.N, B.N и X (а также увеличение X и N в каждом из потоком) происходит не атомарно. Прикреплённый файлThreadLockDemo.zip (206,75 Кбайт, скачиваний: 62) |
Сообщ.
#41
,
|
|
|
Цитата Fr0sT @ Я не большой знаток устройства процов, но мне кажется, что инструкция, хотя бы снаружи - вне зависимости от того, как реализовано унутрях - атомарна. Иначе любому менеджеру потоков/процессов пришлось бы лочить каждую инструкцию, чтобы не дай бог не запустить другой поток, пока выполняется инструкция прежнего. Цитата Fr0sT @ Однако если операция на Паскале транслируется в одну инструкцию (напр., Inc без получения старого значения), то необходимости в interlocked нет. Любая инструкция процессора (кроме строковых операций с rep) атомарна в плане прерывания\исключения, т.е. либо она выполняется полностью, либо отменяется полностью. Поэтому в однопроцессорной системе инструкции, выполняющие чтение и запись в память, также являются атомарными и в плане чтения\записи (если не рассматривать специальные экзотические случаи, когда память м.б. изменена не самим процессором, а "внешними агентами" - видеокартой, контроллером DMA и т.п.). Но в многопроцессорных (многоядерных) системах x86 эти инструкции в общем случае не являются атомарными в плане чтения\записи, т.к. между (микро)операциями чтения и записи, память может быть изменена другим логическим процессором. Например, если два потока на разных логических процессорах делают Inc одной и той же переменной, то она в итоге может измениться как на 2, так и на 1 (если оба потока одновременно прочитают одно и тоже значение). Если же выполняется lock inc, то гарантируется, что между чтением и записью ни один другой поток не сможет не только изменить значение переменной, но и даже прочитать ее ("неконсистентное" значение). Поэтому два lock inc в разных потоках гарантированно увеличивают значение переменной на 2, а не на 1-2, как при простом Inc. |
Сообщ.
#42
,
|
|
|
Цитата Fr0sT @ Interlocked-функции, зашитые в DLL, сделаны не для паскаля/Delphi. И даже не для C++, т.к. проще сделать в C++ и в Delphi такую функцию (с xadd), чем пихать её в DLL. А по поводу возврата старого значения, так почему бы не сделать так:Если с возвратом старого значения - две. Если старое не нужно - то одна, и тогда и InterlockedIncrement не особо нужен. X := A; // Получаем старое значение A := A + 1; // Увеличиваем на 1 Цитата leo @ Добавлю, что под микрооперацией подразумевается внутреняя операция в процессоре (ядре), т.е. Temp = [M32]; Temp++; [M32] = Temp.Но в многопроцессорных (многоядерных) системах x86 эти инструкции в общем случае не являются атомарными в плане чтения\записи, т.к. между (микро)операциями чтения и записи, память может быть изменена другим логическим процессором. Например, если два потока на разных логических процессорах делают Inc одной и той же переменной, то она в итоге может измениться как на 2, так и на 1 (если оба потока одновременно прочитают одно и тоже значение). Собственно, реальное поведение демонстрирует приведённая выше программа. А вот ещё одна. Использование CMPXCHG с LOCK'ом и без для SpinLock'ов (организации критических секций - участков кода, доступ к которому должен иметь только 1 поток). Прикреплённый файлThreadSpinLockDemo.zip (210,96 Кбайт, скачиваний: 63) Добавлено p.s. Справедливости ради хочу сказать, что есть 1 инструкция, которые всегда делает Bus-Lock (т.е. её не нужно предварять префиксом LOCK) - это XCHG (не CMPXCHP, не CMPXCHG8B и пр, а обычная XCHG). |
Сообщ.
#43
,
|
|
|
Вот из документации Intel:
Цитата Немного странная конструкция, особенно на ассемблере, её можно упростить.5 Examples Two examples of generic spin-wait loops are given here. 5.1 A Sample Spin-wait Lock get_lock: mov eax, 1 xchg eax, A ; Try to get lock cmp eax, 0 ; Test if successful jne spin_loop critical_section: <critical section code> mov A, 0 ; Release lock jmp continue spin_loop: pause ; Short delay cmp 0, A ; Check if lock is free jne spin_loop jmp get_lock continue: 5.2 Another Spin-wait Sample // Come here if we didn’t get the lock on the first try. for (;;) { for (int i=0; i < SPIN_COUNT; i++) { if ( (i & SPIN_MASK) == 0 && m_dwLock == UNLOCKED && InterlockedExchange( &m_dwLock, LOCKED )== UNLOCKED) return; #ifdef _X86_ _mm_pause(); #endif } SleepForSleepCount( cSleeps++ ); } Инструкция pause, как пишут, повышает производительность процессора за счёт оптимизация работы процессора с памятью и уменьшения мощности процессора (потребления энергии). Однако на загрузку процессора это не влияет. (Прикол, что эту инструкцию можно запустить даже на 8086, т.к. это, по сути rep nop ) Кстати, leo, посоветоваться с тобой хочу. Вот старт SpinLock'а: procedure StartSpinLock(var X: Integer); begin while InterlockedExchange(X, 1) <> 0 do begin asm pause end; {$IFNDEF NoSleepSpinloops} Sleep(1) {$ENDIF} end end; Так вообще корректно делать? |
Сообщ.
#44
,
|
|
|
Идеально
procedure StartSpinLock(var Key: Integer); var i: Integer; begin i := 0; while InterlockedExchange(Key, 1) <> 0 do begin asm pause end; if HiWord(i) = 0 then Inc(i) else Sleep(1) end end; procedure FinishSpinLock(var Locker: Integer); begin Key := 0 end; |
Сообщ.
#45
,
|
|
|
У меня вот тут вопрос возник: будет ли обычное 32-битное присвоение (mov [X],eax), если переменная не выровнена по границе памяти, атомарным? Т.е. половина переменной находится в одной части памяти, половина - в другой.
|
Сообщ.
#46
,
|
|
|
Если адрес переменной не выравнен на ее размер, то атомарность чтения\записи не гарантируется. Хотя в x86 это проявляется только в случае пересечения границ кэш-линеек (64 байта).
|
Сообщ.
#47
,
|
|
|
Цитата Jin X @ У меня вот тут вопрос возник: будет ли обычное 32-битное присвоение (mov [X],eax), если переменная не выровнена по границе памяти, атомарным? Во-во-во, Вы уже начинаете задаваться правильными вопросами Чтобы заинтриговать, добавлю, что даже если операции были бы атомарны, это всё равно НЕ гарантировало бы правильности результата, по причине... Да-да, т.е. операции по отдельности потокобезопасны, но если собрать их вместе, получается чехарда. Вопрос: кто в этом виноват? |
Сообщ.
#48
,
|
|
|
Цитата Mr.Delphist @ Не понял мысли.Чтобы заинтриговать, добавлю, что даже если операции были бы атомарны, это всё равно НЕ гарантировало бы правильности результата, по причине... Да-да, т.е. операции по отдельности потокобезопасны, но если собрать их вместе, получается чехарда. Вопрос: кто в этом виноват? Почему чехарда? Одна атомарная операция обеспечивает вход в критическую секцию, а дальше делай что хочешь, какие проблемы? Собственно, вопрос про mov в том, сил может, лучше использовать залоченную инструкцию, скажем, xchg (InterlockedXXX)? |
Сообщ.
#49
,
|
|
|
Если в деле участвуют критические секции, то в самом деле чехарды не будет. Но если просто понадеяться на факт атомарности чтения/записи и попытаться сделать на этом логику программы, то ой: данный критерий является необходимым, но вовсе не достаточным.
|
Сообщ.
#50
,
|
|
|
Цитата Jin X @ Я добавил Sleep(1), чтобы снизить нагрузку на процессор, потому что иначе цикл грузит его на максимум (Sleep(0) тоже не помогает). Так вообще корректно делать? Что именно? Использовать Sleep не только корректно, но и необходимо, чтобы не греть попусту процессор в ожидании у моря погоды. А вот использовать spin-wait loop на больших промежутках времени - это э-э-э... мягко говоря, кривой дизайн. Спин-лупы используются только на коротких интервалах с небольшим числом повторений (исчисляемых максимум десятками, но никак не десятками тысяч, как в твоем "идеальном" HiWord(i) = 0). В противном случае, они не только попусту нагружают и греют процессор, но и могут "тормозить сами себя" - если второй поток находится в ожидании свободного процессора, или выполняется на том же ядре с HT, то длительный спин-луп лишь затягивает запуск или тормозит исполнение этого потока. Цитата Jin X @ Инструкция pause, как пишут, повышает производительность процессора за счёт оптимизация работы процессора с памятью и уменьшения мощности процессора (потребления энергии). Однако на загрузку процессора это не влияет. Во-первых, pause влияет на реальную загрузку процессора (с точки зрения Intel), т.к. проц не молотит с бешеной скоростью бестолковые инструкции по несколько штук за такт, а вставляет между ними паузы. Собственно в этом и смысл spin-wait - выдержать некоторую разумную паузу, не передавая управление ОС, но и не перегревая проц бестолковыми операциями. А с точки зрения ОС загрузка процессора конкретным потоком уменьшается только тогда, когда поток спит в недрах ОС, причем счет времени сна ведется довольно "топорно" - по тикам сис.таймера с дефолтным интервалом в ~15 мс. Во-вторых, обрати внимание, что в примерах Intel pause используется в сочетании с простыми операциями сравнения (cmp в 5.1 и (i & SPIN_MASK) == 0 в 5.2), которые могут выполняться в цикле с огромной скоростью (с перекрытием итераций за счет внеочередного\спекулятивного исполнения). Поэтому pause тут реально помогает снизить частоту (бестолковых) проверок. Ты же используешь pause в сочетании непосредственно с InterlockedExchange без доп.простых проверок, поэтому эффективность pause у тебя гораздо ниже, т.к. lock-операции сами по себе ужасно тормозные, и несколько тактов pause не идут ни в какое сравнение с сотней-другой тактов на InterlockedExchange. PS: Такое ощущение, что ты продолжаешь изобретать велосипеды в стиле "Саги об X, Y и Z". Вместо использования готовых объектов синхронизации (типа критической секции), используешь интерлоки где нужно и не нужно (например, в SetNotyfy), а в итоге все равно можешь нарваться на грабли (например, в Close, если промахнешься с таймаутом и выставишь FreeOnTerminate уже завершенному потоку). |
Сообщ.
#51
,
|
|
|
Цитата leo @ Ну ок, HiWord, допустим, это много, но...Что именно? Использовать Sleep не только корректно, но и необходимо, чтобы не греть попусту процессор в ожидании у моря погоды. А вот использовать spin-wait loop на больших промежутках времени - это э-э-э... мягко говоря, кривой дизайн. Спин-лупы используются только на коротких интервалах с небольшим числом повторений (исчисляемых максимум десятками, но никак не десятками тысяч, как в твоем "идеальном" HiWord(i) = 0). В противном случае, они не только попусту нагружают и греют процессор, но и могут "тормозить сами себя" - если второй поток находится в ожидании свободного процессора, или выполняется на том же ядре с HT, то длительный спин-луп лишь затягивает запуск или тормозит исполнение этого потока. Цитата leo @ почему кривой, если он разбавлен Sleep(1)? Разве что сам Sleep(1) мне не очень нравится.А вот использовать spin-wait loop на больших промежутках времени - это э-э-э... мягко говоря, кривой дизайн. Ну ещё хорошо бы locked-операции разбавить обычными - это я уже понял. Цитата leo @ Почему же не нужно?Вместо использования готовых объектов синхронизации (типа критической секции), используешь интерлоки где нужно и не нужно (например, в SetNotyfy) Если SetNotify будет менять переменную FNotify в тот момент, когда её будет читать DoNotify (для исполнения), может получиться конфуз. Т.к. SetNotify изменила тип действия, но ещё не изменила адрес (процедуры и пр.), а DoNotify уже её прочитала. Цитата leo @ Согласен. Как минимум DoNotify лучше перенести ниже, чем FreeOnTerminate и... ну не знаю, посоветуй тогда что-нибудь, как лучше сделать в этом месте? а в итоге все равно можешь нарваться на грабли (например, в Close, если промахнешься с таймаутом и выставишь FreeOnTerminate уже завершенному потоку). Добавлено Цитата leo @ Ок, какую-то внутрненюю разгрузку он, может, и делает, но если запуститьВо-первых, pause влияет на реальную загрузку процессора (с точки зрения Intel) asm xor ecx,ecx @:pause loop @ end; Добавлено Хотя, я сейчас запустил 8 потоков с @:jmp @ и 8 потоков с @:pause jmp @ и посмотрел на температуру - нагрев одинаковый, что в том случае, что в другом. И тормоза системы одинаковые. Так что, может какие-то процессы он там и оптимизирует, а вот по поводу разгрузки вопрос... |
Сообщ.
#52
,
|
|
|
Pavia на другом форуме написал:
Цитата Думаю что это будет оптимальнее, хотя и не всегда. С начало 1000 раз Sleep(0), а потом уже Sleep(1). Sleep(0)- нужен для микросекунд. Вернее мы сможем выиграть в производительности на микросекундном диапазоне. Процессор это не разгрузит, а вот производительности добавит. А вот Sleep(1) уже разгрузит процессор. За счёт чего выигрыш? Принудительно смена потоков происходит раз 10-250 мс. В некоторых случаях 1 мс. Первый поток ждет пока второй поток завершит работу, а второй поток не работает так как квант времени занят первым. Поэтому мы должны сами спровоцировать переключения контекстов иначе так мы будем ждать минимум 10 мс! До принудительного переключения. Что очень долго. Почему ОС не перераспределяет по ядрам потоки? Из-за энергосбережения она делает это редко. ОС в среднем перераспределяет процессы по ядрам если какой либо работает(висит) дольше 125 или 250 мс (не помню точно вроде второе число). Я создал 10 потоков и у каждого в цикле написал Sleep(0) потом поменял на SwitchToThread. Результат аналогичен Process Explorer показал 5 милионов переключений на 8 поточном процессоре. 1,6 мкс на переключение. А если сделать sleep(1) то от 300 до 1000 раз. Как видите разница на 3 порядка! Я ответил: Цитата Так оно и понятно, что будет на 3 порядка меньше. Ведь Sleep(1) делает ещё и задержку. Тогда, наверное, стоит сделать так: X (скажем, 256) полноценных циклов с pause, затем SwitchToThread. Всё это повторяем Y раз (скажем, 4), затем заменяем SwitchToThread на Sleep(1). И всё заново. Получается один Sleep(1) на 1024 циклов, но до него ещё 3 SwitchToThread. Я потестил этот механизм. И на i5 2500K, и на нетбуке со слабым процем (Atom) такой код с 256 потоками грузит проц меньше, чем на 1% (примерно такая же история с 1024 потоками при кол-ве циклов до Sleep(1) 32*4=256). p.s. Слишком частый вызов SwitchToThread тоже добавляет загрузки процу. Например 32*32 будет уже грузит i5 на 40-50% (256 потоков). Что думаете? Добавлено Или вот: ( ( 265 spin-циклов, затем SwitchToThread ) * 4 раза, затем Sleep(1) ) * 4 раза. Итого выходит 4096 циклов. Не так много, но и не мало. После этого переходим на Sleep(1) после каждой проверки, максимально разгружая таким образом проц. Добавлено Хочется соптимизировать вот эти числа: 256, 4 и 4 |
Сообщ.
#53
,
|
|
|
Цитата Jin X @ Думаю что это будет оптимальнее, хотя и не всегда. С начало 1000 раз Sleep(0), а потом уже Sleep(1). Sleep(0)- нужен для микросекунд. Вернее мы сможем выиграть в производительности на микросекундном диапазоне. Процессор это не разгрузит, а вот производительности добавит. А вот Sleep(1) уже разгрузит процессор. Микросекунды на Win32 API?! Господа, не надо так хорошо думать о Билли Г А если серьёзно, то идём и читаем маны: https://msdn.microsoft.com/en-us/library/wi...8(v=vs.85).aspx Цитата A value of zero causes the thread to relinquish the remainder of its time slice to any other thread that is ready to run. If there are no other threads ready to run, the function returns immediately, and the thread continues execution. Т.е. нулевой интервал просто заставляет планировщик задач досрочно переключить контекст, не дожидаясь истечения запланированного кванта времени (обычно порядка 20-50 миллисекунд). Всё, точка, никаких микросекунд (столь мелкие интервалы появились лишь в multimedia API и по сути и будут спин-локами). Update: ах да, ещё про спин-локи: они также были добавлены в функции по захвату критических секций и прочих примитивов, когда стала популярна многоядерность. Т.е. если раньше при сигнале "занято" был безусловный выход из попытки захвата ресурса, то позже туда добавили спин-лок и вторую попытку захвата: * ой, занято * подождём на спин-локе немножко, вдруг занято другим ядром и ненадолго * ой, опять занято - видать, это надолго или нашим же ядром залочено, выходим |
Сообщ.
#54
,
|
|
|
Цитата Jin X @ Почему же не нужно? Если SetNotify будет менять переменную FNotify в тот момент, когда её будет читать DoNotify У тебя SetNotify вызывается до создания потока... Цитата Jin X @ Согласен. Как минимум DoNotify лучше перенести ниже, чем FreeOnTerminate и... ну не знаю, посоветуй тогда что-нибудь, как лучше сделать в этом месте? Уже посоветовал простой вариант - использовать FreeOnTerminate:=true и критическую секцию для проверки\установки FResult и посылки PostThreadMessage. Хочешь не примитивную секцию, а со спин-лупом - пожалуйста, юзай InitializeCriticalSectionAndSpinCount или SetCriticalSectionSpinCount. Всё уже за тебя и для тебя сделано ребятами из MS - зачем изобретать самопальные велосипеды? Цитата Jin X @ но если запустить ... и посмотреть на загрузку в диспетчере задач, то загруз там будет максимальным... Может, конечно, это с той точки зрения, что ядро полностью занято текущим потоком... Не может, а точно. Тебе уже объяснили, что "загруз" в диспетчере задач - это не реальный "вычислительный загруз" процессора, а процент времени, когда поток подключен к процессору, а не спит в недрах ОС. Поэтому, не отдавая управление ОС функциями Sleep, Wait... и т.п., снизить этот "диспетчерский загруз" невозможно. Хоть pause используй, хоть hlt (если сможешь), ОС по любому будет считать, что твой поток работает и "грузит" процессор. Наглядный тому пример, процесс "Бездействие системы" в диспетчере задач, который в отсутствии гипер-активных процессов\потоков "грузит" проц почти на 100% - ниче так "бездействие"?! Цитата Jin X @ Так что, может какие-то процессы он там и оптимизирует, а вот по поводу разгрузки вопрос... Сдалась тебе эта загрузка\разгрузка? При однократном (или 4-х кратном) вызове Sleep(1) ты все равно никакой "разгрузки" в диспетчере не успеешь заметить (глазом моргнуть). Дело не в загрузке, а в целесообразности\оптимальности - спин-луп имеет смысл крутить короткое время в предположении, что другой поток "вот-вот" освободит лок. Если это крит.секция, то время спин-ожидания д.б. сравнимо со временем выполнения кода, заключенного в эту секцию. Если же "что-то пошло не так" и лок не освобождается, значит поток уснул и продолжать крутить луп не имеет смысла. Нужно наоборот, передать управление системе в надежде на то, что отключение твоего потока ускорит активацию потока, которого ты ждешь |
Сообщ.
#55
,
|
|
|
Цитата leo @ Слушай, точно. Вот прогон! Видимо, хотел сначала в Execute это сделать, а потом уже по инерции оставил всё как есть У тебя SetNotify вызывается до создания потока... Цитата leo @ Т.е. ты предлагаешь делать FreeOnTerminate := True всегда при вызове Close и не ждать завершения (WaitForSingleObject)? Уже посоветовал простой вариант - использовать FreeOnTerminate:=true и критическую секцию для проверки\установки FResult и посылки PostThreadMessage. |
Сообщ.
#56
,
|
|
|
Можно сделать проще:
procedure TMsgBoxTimeoutNotifyThread.Execute; var Res: TMsgBoxTimeoutResult; begin with MsgBox do with FParams do begin Res := MessageBoxTimeout(hWnd, PChar(Text), PChar(Title), Flags, LangId, Timeout); if InterlockedCompareExchange(Pointer(FResult), Pointer(Res), Pointer(MBTR_DISPLAYING)) = Pointer(MBTR_DISPLAYING) then // Реузльтат будет другим (не MBTR_DISPLAYING), если окно закрыто методом Close DoNotify(mbtOnClose) else begin FreeOnTerminate := True; FThread := nil end end end; procedure TMsgBoxTimeoutNotify.Close(Res: TMsgBoxTimeoutResult = MBTR_BREAKED); begin Result := True; if Res = MBTR_DISPLAYING then Res := MBTR_BREAKED; if InterlockedExchange(Integer(FResult), Integer(Res)) = Integer(MBTR_DISPLAYING) then begin PostThreadMessage(FThread.ThreadID, WM_QUIT, 0, 0); DoNotify(mbtOnClose) end end; И никакие критические секции не нужны... |
Сообщ.
#57
,
|
|
|
Цитата Jin X @ Можно сделать проще: ... Уф-ф, ну сколько можно путаться в трех соснах?! Ну выполнил ты в Close проверку на InterlockedExchange, а винде вдруг приспичило усыпить твой поток сразу после этой проверки. Дальше ясно, что будет? Если нет, то объясняю - ты просыпаешься и как ни в чем не бывало лезешь в FThread.ThreadID (как будто весь мир спал вместе с тобой на критической секции), а метод Execute за это время установил FThread:=nil и возможно удалил объект потока по FreeOnTerminate ... Цитата Jin X @ И никакие критические секции не нужны... Нужны. В методе Close проверка состояния FResult и последующие обращения к FThread\ThreadID должны выполняться "атомарно", т.е. так, чтобы метод Execute не смог обнулить FThread и уничтожить объект между проверкой состояния и обращением к переменной FThread. Поэтому тут нужна либо стандартная крит.секция, либо ее аналог на спин-локе в методе Execute с проверкой некого состояния Breaking (которое можно добавить к значениям FResult или же использовать отдельную переменную). Избавиться от крит.секции можно только в случае если 1) не удалять объект объект потока по FreeOnTerminate (или сохранять его ID в отдельной переменной) и 2) предположить, что при завершении потока, его ID не будет использован ОС повторно на коротком интервале времени (хотя официально это не гарантируется). PS: Вообще не понятно, для чего тебе сдалось автоудаление объекта в одном частном случае и не удаление в другом? Почему не использовать однотипный подход - либо FreeOnTerminate всегда true, либо всегда false? Какой-то червяк мелочной псевдо-оптимизации покоя не дает? |
Сообщ.
#58
,
|
|
|
upd: Сообщение исправлено (но код оставил для истории), см. ниже примечания.
Цитата leo @ Ну выполнил ты в Close проверку на InterlockedExchange, а винде вдруг приспичило усыпить твой поток сразу после этой проверки. Дальше ясно, что будет? Если нет, то объясняю - ты просыпаешься и как ни в чем не бывало лезешь в FThread.ThreadID (как будто весь мир спал вместе с тобой на критической секции), а метод Execute за это время установил FThread:=nil и возможно удалил объект потока по FreeOnTerminate ... Цитата leo @ Не успел я исправить код до твоего прихода, ну да ладно. В общем, FThread := nil я перенёс в Close:Нужны. В методе Close проверка состояния FResult и последующие обращения к FThread\ThreadID должны выполняться "атомарно", т.е. так, чтобы метод Execute не смог обнулить FThread и уничтожить объект между проверкой состояния и обращением к переменной FThread. procedure TMsgBoxTimeoutNotifyThread.Execute; var Res: TMsgBoxTimeoutResult; begin with MsgBox do with FParams do begin Res := MessageBoxTimeout(hWnd, PChar(Text), PChar(Title), Flags, LangId, Timeout); if InterlockedCompareExchange(Pointer(FResult), Pointer(Res), Pointer(MBTR_DISPLAYING)) = Pointer(MBTR_DISPLAYING) then // Результат будет другим (не MBTR_DISPLAYING), если окно закрыто методом Close DoNotify(mbtOnClose) else FreeOnTerminate := True end end; procedure TMsgBoxTimeoutNotify.Close(Res: TMsgBoxTimeoutResult = MBTR_BREAKED); begin if Res = MBTR_DISPLAYING then Res := MBTR_BREAKED; if InterlockedExchange(Integer(FResult), Integer(Res)) = Integer(MBTR_DISPLAYING) then begin PostThreadMessage(FThread.ThreadID, WM_QUIT, 0, 0); FThread := nil; DoNotify(mbtOnClose) end end; Цитата leo @ Не гарантируется, но повторюсь, что иначе добрая половина программ была бы потенциально небезопасной. Поэтому этот момент должен быть учтён мелкомягкими (собственно, ты сам об этом и говорил).предположить, что при завершении потока, его ID не будет использован ОС повторно на коротком интервале времени (хотя официально это не гарантируется). Цитата leo @ Да, leo, ты прав, это нафиг не нужно. У меня уже столько было различных переделок, что я кое-где запутался и оставил ненужные рудименты. Сейчас всё должно быть ок (только не тут, а см. ниже)! PS: Вообще не понятно, для чего тебе сдалось автоудаление объекта в одном частном случае и не удаление в другом? Почему не использовать однотипный подход - либо FreeOnTerminate всегда true, либо всегда false? Какой-то червяк мелочной псевдо-оптимизации покоя не дает? Если я сделаю FreeOnTerminate := False, то после Close я не буду уверен, что объект, присвоенный переменной Thread остановлен, а открытое им окно 100% закрыто. Придётся городить городьбу опять с WaitForSingleObject, критическими секциями и т.п. И возвращать либо True, либо False в зависимости от того, закрылось реально окно или нет. Может, это и неплохо, но большого смысла в этом я уже не вижу. upd: 5 сек... возможно, действительно FreeOnTerminate := True делать уже не смысла (учитывая изменения)... Сейчас чайку попью и, возможно, удалю... |
Сообщ.
#59
,
|
|
|
Чтобы быть уверенным, что тред остановлен, надо сначала взвести флаг Terminated, а затем вызвать Join().
P.S. Да, вызов блокирующий - поэтому надо быть осторожным в написании метода Execute, не уходить в долгие вычисления. |
Сообщ.
#60
,
|
|
|
Цитата Mr.Delphist @ Сам по себе флаг Terminated ничего не гарантирует. Тем более, что в том самом потоке, напомню, выполняется MessageBox.Чтобы быть уверенным, что тред остановлен, надо сначала взвести флаг Terminated, а затем вызвать Join(). P.S. Да, вызов блокирующий - поэтому надо быть осторожным в написании метода Execute, не уходить в долгие вычисления. А Join в Delphi7, по крайней мере, нет. Может, WaitFor ты имел в виду? |
Сообщ.
#61
,
|
|
|
Частично выкладываю что получилось...
Описание: // Ждать закрытия окна сообщения (нажатия кнопки или срабатывания таймаута). // Во время ожидания метод постоянно вызывает Sleep(1), а также функцию/метод Idle, если он(а) задан(а) каждые Period миллисекунд (первый вызов осуществляется сразу). // При значении Period = INFINITE вызов Idle выполняется только 1 раз. Если функция/метод вернёт False, цикл ожидания прервётся, а метод вернёт значение MBTR_WAITBREAKED. // В остальных случаях возвращает результат, т.е. свойство GetResult (аналогично функциям MessageBoxTimeout, MsgBoxTimeout). function Wait(Idle: TMsgBoxTimeoutIdleFunc = nil; Period: DWord = 0): TMsgBoxTimeoutResult; overload; function Wait(Idle: TMsgBoxTimeoutIdleMethod; Period: DWord = 0): TMsgBoxTimeoutResult; overload; // Принудительно закрыть окно сообщения, установив значение результата Res (при указании значения MBTR_DISPLAYING оно будет заменено на MBTR_BREAKED). // Результат устанавливается вне зависимости от состояния окна (открыто или закрыто). Если окно сообщения уже закрыто, ничего страшного не произойдёт. // ВНИМАНИЕ: Данный метод лишь посылает потоку команду закрыть окно, но не ждёт, когда это реально произойдёт, поэтому нет гарантии, что после завершения метода окно уже будет // закрыто (хотя это должно произойти очень быстро). В любом случае можно не опасаться вызывать метод Show/ShowEx сразу после метода Close, т.к. окно будет считаться уже закрытым. // Данный метод не вызывает метод Free! procedure Close(Res: TMsgBoxTimeoutResult = MBTR_BREAKED); // Перед уничтожением потока вызвать метод Close и освободить поток. destructor Destroy; override; // Используемый в работе объект потока. Предоставляется на страх и риск программиста (например, для доступа к окну сообщения)! // Может быть равен nil, если поток ещё ни разу не запускался. В остальных случаях объект не освобождается, а свойство не обнуляется, даже если окно уже закрыто (в т.ч. методом Close). // ВНИМАНИЕ: Уничтожение потока в обход метода TMsgBoxTimeout.Close (например, через TerminateThread) или освобождение объекта Thread (Thread.Free) приведёт к некорректной дальнейшей работе и исключениям! property Thread: TThread read FThread; Реализация: procedure TMsgBoxTimeoutNotifyThread.Execute; var Res: TMsgBoxTimeoutResult; begin with MsgBox do with FParams do begin Res := MessageBoxTimeout(hWnd, PChar(Text), PChar(Title), Flags, LangId, Timeout); if InterlockedCompareExchange(Pointer(FResult), Pointer(Res), Pointer(MBTR_DISPLAYING)) = Pointer(MBTR_DISPLAYING) then // Результат будет другим (не MBTR_DISPLAYING), если окно закрыто методом Close DoNotify(mbtOnClose) end end; function TMsgBoxTimeout.ReadyToShow(Flags: DWord): Boolean; begin Result := True; if FResult = MBTR_DISPLAYING then case Flags and MBTF_REACTION_MASK of MBTF_WAIT: Wait; MBTF_FAIL: Result := False else Close end end; function TMsgBoxTimeoutNotify.ShowEx(hWnd: THandle; const Text, Title: String; Flags, Timeout: DWord; Ntf: TMsgBoxTimeoutNotifyRec; const Id: String = ''; LangId: Word = 0): Boolean; begin Result := ReadyToShow(Flags); if Result then begin FreeAndNil(FThread); // Даже если ShowEx вызван сразу после Close, а поток ещё не успел завершить работу, деструктор TThread подождёт его завершения FParams.hWnd := hWnd; FParams.Text := Text; FParams.Title := Title; FParams.Flags := Flags and not MBTF_FLAGS_MASK; FParams.Timeout := Timeout; FParams.LangId := LangId; FId := Id; if FId = '' then FId := GenerateId; FNotify := Ntf; FThread := TMsgBoxTimeoutNotifyThread.Create(True); FResult := MBTR_DISPLAYING; with TMsgBoxTimeoutNotifyThread(FThread) do begin MsgBox := Self; DoNotify(mbtOnShow); Resume end end end; function TMsgBoxTimeout.Wait(Idle: TMsgBoxTimeoutIdleFunc = nil; Period: DWord = 0): TMsgBoxTimeoutResult; var T1, T2: Int64; begin T1 := 0; while FResult = MBTR_DISPLAYING do begin if @Idle <> nil then begin T2 := GetTickCount; if (T1 = 0) or ((T2-T1 >= Period) and (Period <> INFINITE)) then begin if not Idle(Self) then begin Result := MBTR_WAITBREAKED; Exit end; T1 := T2 end end; Sleep(1) end; Result := FResult end; procedure TMsgBoxTimeoutNotify.Close(Res: TMsgBoxTimeoutResult = MBTR_BREAKED); begin if Res = MBTR_DISPLAYING then Res := MBTR_BREAKED; if InterlockedExchange(Integer(FResult), Integer(Res)) = Integer(MBTR_DISPLAYING) then begin PostThreadMessage(FThread.ThreadID, WM_QUIT, 0, 0); DoNotify(mbtOnClose) end end; destructor TMsgBoxTimeout.Destroy; begin Close; FThread.Free; // Если окно уже было закрыто до вызова Close inherited end; |