Блог. Двадцать пять лет Делфи-практики
В этом блоге я буду публиковать ответы на вопросы, которые постоянно приходится повторять на форуме.
Здесь можно это сделать более развернуто и спокойно.
Все, что здесь написано, не является истиной в последней инстанции, скорее, это результат моих размышлений над архитектурой проектов, маленьких и больших, которых я сделал на Делфи более дюжины.
Начав с Делфи-2 двадцать пять лет назад, я прошел все версии, испробовал массу технологий, включая работу с БД, с графикой DirectX, связью с серверами и интернетом, разработку на Андроид и IOS, и многое, многое другое.
________________________________________ ________________________________________ ____
P.S. все, о чем здесь написано, всего лишь измышления из головы.
совпадения с реальными людьми и фактами случайны.
В этом блоге я буду публиковать ответы на вопросы, которые постоянно приходится повторять на форуме.
Здесь можно это сделать более развернуто и спокойно.
Все, что здесь написано, не является истиной в последней инстанции, скорее, это результат моих размышлений над архитектурой проектов, маленьких и больших, которых я сделал на Делфи более дюжины.
Начав с Делфи-2 двадцать пять лет назад, я прошел все версии, испробовал массу технологий, включая работу с БД, с графикой DirectX, связью с серверами и интернетом, разработку на Андроид и IOS, и многое, многое другое.
________________________________________ ________________________________________ ____
P.S. все, о чем здесь написано, всего лишь измышления из головы.
совпадения с реальными людьми и фактами случайны.
Про потоки
Запись от krapotkin размещена 13.08.2017 в 10:03
Показов 32884
Комментарии 59
Метки delphi
|
Сразу, в первой строке. Потоки не должны ничего читать и писать в формах и компонентах!!! Все, что нужно им для работы, задавайте им до старта, и забирайте результат после выполнения Сама концепция потоков проста. Вы можете мыслить их как корабль, отправляющийся в дальнее плавание. На Марс, например. Вроде как и можно устроить сеанс связи с Хьюстоном, но это геморрой, поэтому нужно на корабль погрузить все до старта, и только после возврата вы сможете разобрать трофеи. Лучше всего - запустить и забыть. Даже если вы запустите 20 кораблей, то возвращаться они будут по одному. И на каждом будет написан его бортовой номер, чтобы вы могли их отличать. Как-то так. И точно потоки не помогут вам ничего рисовать на экране или двигать компоненты. Зато они могут все рассчитать для этого, создать битмапы и заполнить какие-то структуры данных. А вы уже в главном потоке все это будете использовать для вывода на экран. Часто путают поток ОС с классом TThread Это не одно и то же. TThread призван запомнить все данные, нужные в потоке, запустить поток в операционной системе, выполнить действие в нем и вернуть результат. Жизнь потока состоит из трех этапов 1. создание и инициализация (это еще не поток с т.з. ОС) 2. запуск настоящего потока (вот тут настоящий поток ОС работает) 3. окончание работы потока Обратите внимание! В пунктах 1 и 3 работа идет в главном потоке, там, где формы и пользовательский ввод. Только 2-я часть уходит в автономное плавание, забирая с собой все переменные, которые вы ей насовали с собой в части 1 Начнем 1. Нужно создать своего наследника TThread, имеющего все нужные поля. Заполнить эти поля. Запустить поток. Примечание. Все дополнительные классы выносите в дополнительные юниты! Пусть класс TMyThread будет жить в UMyThread.pas UMyThread мы должны прописать в uses у формы, откуда будет запускать свои потоки. Юнит UMyThread ничего о форме знать НЕ ДОЛЖЕН! 2. Самая стандартная задача - сходить в потоке в интернет и что-то скачать. Результат вернуть и использовать в форме 2.1. Залогиниться где-то один раз. 2.2. Скачать со 100 страниц информацию и опять же что-то с ней сделать Разберем задачу 2.2. Она посложнее Лирическое отступление Класс TThread имеет замечательное свойство FreeOnTerminate. Если установить его в true, то не нужно будет хранить ссылку на созданный объект, чтобы потом удалить его вручную. Создали, запустили и забыли о нем. Память освободится сама. Сделать это лучше всего, переопределив конструктор нашего наследника, где и вписать FreeOnTerminate := true; В том же конструкторе мы должны вызвать конструктор предка - класса TThread - с параметром true Это значит, он не запустится сразу же, а даст нам сначала заполнить поля объекта, а мы потом его запустим командой Resume. (В свежих версиях Delphi вместо Resume нужно вызывать Start) 3. Главное. Как мы получим данные от потока? У класса TThread есть обработчик OnTerminate. Если на него назначить нашу собственную процедуру, то она будет вызываться после Execute для каждого потока. Это удобно. Дважды удобно то, что OnTerminate работает уже в главном потоке и не надо ничего делать для синхронизации, можно прямо писать в компоненты, например выводить в мемо, или заполнять структуры данных, не боясь, что другие потоки тоже лезут туда. Все синхронизировано по факту. Поэтому это лучший метод возврата результата из потока. Итак. Объявим метод формы с любым именем, лишь бы параметры были Sender: TObject
Рекомендуется не заполонять систему своими потоками. 3000 потоков вашей программы почти с гарантией поставят систему на колени. Поэтому одновременно запустим N потоков. После окончания каждого одного будем запускать один следующий, пока не стартуем нужное нам количество. При этом активно всегда будет не более N потоков. Итак, сценарий. Запустить N потоков. При завершении - проверять, нужно ли запустить еще, или нет. Также проверять, если все потоки завершены, то подвести итог. Кнопку запуска потоков сделаем неактивной до времени, когда закончится последний поток, чтобы нельзя было запустить процесс еще раз. Реализация Кликните здесь для просмотра всего текста
Кликните здесь для просмотра всего текста
Часто задают вопрос, что делать, пока потоки выполняются. И заводят какие-то безумные циклы
отслеживание мыши, клавиатуры, перерисовка себя любимой, реагирование на всякие сообщения системы. Это ее нормальное состояние. Что-либо еще(!) делать она будет, когда возвращается поток. Например, как в данном случае, когда все потоки завершены, мы выводим запись в мемо. UPD Давайте рассмотрим еще несколько вопросов Пример 1 Мы запустили поток. Он занят чем-то продолжительным но не единым действием, а в цикле. Кликните здесь для просмотра всего текста
например
тогда модифицируем условие
Пример 2 У нас куча потоков и мы хотим остановить их Кликните здесь для просмотра всего текста
Делаем то же самое но используем для этого общую переменную. Я специально говорю "общую" а не "глобальную".
Хотя одно есть разновидность другого. Я очень не рекомендую использовать глобалки в серьезных проектах Это не страшно, но очень не здорово. При этом глобалку нужно использовать простого типа - boolean, integer, тогда доступ к ней можно осуществлять без механизмов синхронизации. Хорошим вариантом будет, например, передать в поток УКАЗАТЕЛЬ на переменную.
Пример 3 Если чекбокс установлен, то в потоке делать то, а иначе делать сё Кликните здесь для просмотра всего текста
Понятно, что никакой чекбокс передавать в поток нельзя. Нам нужен не компонент, а само значение. Вот его мы передадим в поток перед стартом
Пример 4 Любимое. Как из потока менять что-то на форме. Синхронизация. Кликните здесь для просмотра всего текста
Хорошим способом является передача сообщений через SendMessage(); Но тут начнутся проблемы с правильной работой с памятью, когда передаем строки и много другого, и главное - это не кроссплатформенно! Поэтому я обычно выбираю Synchronize. Еще раз подчеркну. Нельзя вызывать формы и их компоненты и еще много чего без синхронизации, т.е. без указания, что эту процедуру можно выполнять только в главном потоке. Но это значит, что этот поток остановится и будет ждать, когда процедура выполнится. Поэтому выполняйте синхронизацию как можно реже и как можно быстрее. Как я уже говорил, мы не даем потоку ссылку на форму. Хотите двигать прогресс-бар, - возьмите ссылку на прогресс-бар и пользуйтесь. Хотите мемо - берите ссылку на мемо. Но помните! Мемо - один из самых тормозных компонентов. 5000 строк - и вся ваша программа 85% времени проводит, добавляя строки в мемо. Итак, цикл, синхронизация, запись в progressBar. Да еще и не каждый проход цикла, чтобы не молотить зря.
upd тема "Я пишу приложение, которое выкачивает весь интернет по списку из Memo. Скажите как." настолько замусолена уже что приложу себе ссылку, чтобы потом легче искать Оптимизация приложения "менеджер закачек" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Метки delphi
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 59
Комментарии
-
КодDelphi 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
type TWorkThread = class(TThread) private FLeftBound, FRightBound: Int64; FResultValue: Int64; function Calculate(LeftBound,RightBound: Int64):Int64; protected procedure Execute; override; public constructor Create(ALeftBound, ARightBound: Int64; CreateSuspended: Boolean); published property ResultValue: Int64 read FResultValue write FResultValue; end; implementation { TWorkThread } constructor TWorkThread.Create( ALeftBound, ARightBound: Int64; CreateSuspended: Boolean); begin inherited Create(CreateSuspended); FLeftBound:= ALeftBound; FRightBound:= ARightBound; FResultValue:= 0; end; procedure TWorkThread.Execute; begin FResultValue:= Calculate(FLeftBound,FRightBound); end; function TWorkThread.Calculate(LeftBound,RightBound: Int64):Int64; begin Result:= LeftBound; // Тут код твоих вычислений, меня харит его писать. end;
Delphi 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
procedure TMainForm.CreateThread(ALeftBound, ARightBound: Int64); var WorkThread:TWorkThread; begin WorkThread:= TWorkThread.Create(ALeftBound, ARightBound,true); WorkThread.FreeOnTerminate:= true; WorkThread.OnTerminate:= TerminateThread; WorkThread.Start; end; procedure TMainForm.TerminateThread(Sender: TObject); var WorkThread:TWorkThread; begin Dec(FThreadCount,1); WorkThread:= Sender as TWorkThread; Inc(FResultValue,WorkThread.ResultValue); if FThreadCount=0 then Caption:= IntToStr(FResultValue); end; procedure TMainForm.Button1Click(Sender: TObject); begin FThreadCount:= 4; // Тут как нибудь делишь весь диапазон вычислений на поддиапазоны: CreateThread(0,99); CreateThread(100,199); CreateThread(200,399); CreateThread(300,499); end;
Запись от Avazart размещена 25.08.2017 в 12:57
-
Запись от Massaraksh7 размещена 25.08.2017 в 13:03
-
Ребята, меня удивляет ваше упорство. Я же сразу сказал, что есть класс задач, не решаемых через OnTerminate, но востребованных. Программе, занимающейся преимущественно сложными вычислениями, и желающей использовать все возможности процессоров не нужен вывод во всякие Captionы. Ей нужен результат в самой программе сразу после окончания очередной порции вычислений. Вот и подумайте лучше, как сделать "нормальный" код для этого случая, если считаете, что мой код гавенный.Запись от Massaraksh7 размещена 25.08.2017 в 13:09
-
Запись от Avazart размещена 25.08.2017 в 13:12
-
Запись от Massaraksh7 размещена 25.08.2017 в 13:55
-
Запись от Avazart размещена 25.08.2017 в 14:21
-
Запись от Massaraksh7 размещена 25.08.2017 в 14:31
-
Запись от Avazart размещена 25.08.2017 в 15:01
-
)) Сразу чувствуется, что человек не программил на JS, Java и других средах, где получение результата функции чаще всего отделено от момента вызова функции))) На этом принципе базируется весь веб и мобильная разработка...
Само требование оформить вызов одной функцией является неверным подходом.
Продолжительные по времени процессы должны запускаться и работать автономно, и только потом вызывать код юзера, когда процесс завершен. При этом результат передается юзеру в лямбда-функцию или коллбек, как в данном случае.
Потому что действия программы должны быть отделены от интерфейса. А интерфейс работает только в главном потоке. И попытки время от времени "дергать" перерисовку и отработку этого интерфейса (Application.processMessages) приведут только к абсолютной нелинейности программы.
Так что ключевой момент здесь - неправильная постановка задачи. Классический XYZ по гансмокеру.Запись от krapotkin размещена 25.08.2017 в 15:14
-
Вообще то это не так.
Тут делается преждевременное допущение что эти типы атомарны, а это может быт не так.
Хотите быть уверены в атомарности операций используйте Interlocked- ф-ции.
https://msdn.microsoft.com/en-... s.85).aspx
Вероятно такие вещи стоит спрятать в класс с помощью class var и class function, а не показывать наружу (но это по ситуации)Запись от Avazart размещена 28.08.2017 в 11:35
-
Запись от krapotkin размещена 28.08.2017 в 13:21
-
Продолжаю перестраивать программу по вашему примеру и подкину еще пару глупых вопросов если можно.
1. Программа все таки висит во время работы - почему?
2. Кол-во потоков сразу летит к SAME_TIME=500; и не играет роли сколько стоит MAX_THREADS=5;
В лог пишет так:
запущен URL 1
запущен URL 2
запущен URL ....
запущен URL 499
запущен URL 500
URL 32:503
URL 34:503
URL 35:503
URL 33:503
URL 36:503
===========================
Работа окончена
URL 37:503
URL 39:503
URL 41:503
URL 40:503
.....
Поменял только работу потока. Поток обращается к сайту в результате Answer:=inttostr(HTTP.ResultCode);
3. Как в var описать переменную th.paramXXX. Без описания ругается, если пробовать описать th.paramXXX или просто paramXXX, то тоже ошибки.
Спасибо.Запись от extrimportal размещена 03.09.2017 в 18:35
-
Запись от krapotkin размещена 03.09.2017 в 19:31
-
Запись от Avazart размещена 05.09.2017 в 11:26
-
Запись от krapotkin размещена 05.09.2017 в 12:28
-
Ссылка на поток все равно нужна, хотя бы для досрочного завершения через Terminate()
(пользователем или при закрытии программы).
И тут при FreeOnTerminate:=True важно понимать что ссылка может в неопределенный момент времени стать невалидной.
Что бы не получить при такой ситуации ошибку доступа к памяти стоит не забывать обнулять ссылку в OnTerminate, а перед обращением к потоку не забывать делать проверку ссылки.Запись от Avazart размещена 06.09.2017 в 12:01
-
Здравствуйте.
Подскажите.
Есть главный поток, который осуществляет взаимодействие с формой.
Есть поток, который осуществляет обмен данными с устройством (в цикле repeat ... until (Terminated)) и результаты работы выводит на форму через Synchronize.
Понадобилось осуществить дополнительно обмен данными с HTTP сервером 2-мя видами запросов (Get и Post).
Запрос Get осуществляет запрос на состояние удаленной системы через интервал времени (примерно 10-20 секунд).
Запрос Post осуществляет передачу показаний с прибора на НТТР сервер (каждые 10 секунд).
Причем интервал каждого запроса может выставятся индивидуально.
Данные запросы были помещены каждый в свой поток и потоки запросов были помещены в поток опроса устройства.
Получается:
1 поток работы с формой
1.1 поток работы с устройством
1.1.1 поток Get
1.1.2 поток Post
1) Можно ли создавать потоки как указано в моем случае ? (Получается
2) Можно ли OnTerminate использовать в Get и Post потоках ?
3) Если в public Get и Post потоках выставить флаг boolean на отсутствие ошибки и проверять его в потоке работы с устройством это может вызвать критическую ошибку ?
Заранее спасибо.Запись от Kumrt размещена 03.06.2019 в 20:11
-
Не по теме:
Пятнадцать лет Делфи-практики
итог:
стал убеждённым сторонником С++:D
Однако,
До сих пор никакие QT и MSVS - не сравнятся с быстрой визуальной разработкой в Delphi
Запись от untyped размещена 03.06.2019 в 20:33
-
Ну, с одной стороны, конечно можно сделать сколько хотите потоков.
Остается вопрос целесообразности.
если ваш поток вообще ничем не занят, то
загрузит ядро процессора на 100%. Оно ж все время что-то там проверяет)))Delphi 1
while not terminated do begin end;
нужно либо ставить Sleep(xxx) либо давать ему какую-то работу
почему бы не совершать post и get запросы в этом потоке?
время с последнего вызова вы всегда можете подсчитать, так что я бы вообще не стал так усложнять систему...
а в целом - все, что прописано в свойствах - то и работает
на всякий случай - OnTerminate будет выполняться в вызвавшем потоке. Обычно это и есть главный, но в вашем примере это не будет так.
тут даже просто количество устройств влияет на архитектуру
если их мало, можно запустить поток для каждого отдельно, если много, то возможно нужен некий пул потоков, которые по очереди опрашивают некую группу устройств.Запись от krapotkin размещена 04.06.2019 в 06:30




