Блог. Двадцать пять лет Делфи-практики
В этом блоге я буду публиковать ответы на вопросы, которые постоянно приходится повторять на форуме.
Здесь можно это сделать более развернуто и спокойно.
Все, что здесь написано, не является истиной в последней инстанции, скорее, это результат моих размышлений над архитектурой проектов, маленьких и больших, которых я сделал на Делфи более дюжины.
Начав с Делфи-2 двадцать пять лет назад, я прошел все версии, испробовал массу технологий, включая работу с БД, с графикой DirectX, связью с серверами и интернетом, разработку на Андроид и IOS, и многое, многое другое.
________________________________________________________________________________ ____
P.S. все, о чем здесь написано, всего лишь измышления из головы.
совпадения с реальными людьми и фактами случайны.
В этом блоге я буду публиковать ответы на вопросы, которые постоянно приходится повторять на форуме.
Здесь можно это сделать более развернуто и спокойно.
Все, что здесь написано, не является истиной в последней инстанции, скорее, это результат моих размышлений над архитектурой проектов, маленьких и больших, которых я сделал на Делфи более дюжины.
Начав с Делфи-2 двадцать пять лет назад, я прошел все версии, испробовал массу технологий, включая работу с БД, с графикой DirectX, связью с серверами и интернетом, разработку на Андроид и IOS, и многое, многое другое.
________________________________________________________________________________ ____
P.S. все, о чем здесь написано, всего лишь измышления из головы.
совпадения с реальными людьми и фактами случайны.
Про потоки
Сразу, в первой строке. Потоки не должны ничего читать и писать в формах и компонентах!!! Все, что нужно им для работы, задавайте им до старта, и забирайте результат после выполнения Сама концепция потоков проста. Вы можете мыслить их как корабль, отправляющийся в дальнее плавание. На Марс, например. Вроде как и можно устроить сеанс связи с Хьюстоном, но это геморрой, поэтому нужно на корабль погрузить все до старта, и только после возврата вы сможете разобрать трофеи. Лучше всего - запустить и забыть. Даже если вы запустите 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. Скажите как." настолько замусолена уже что приложу себе ссылку, чтобы потом легче искать Оптимизация приложения "менеджер закачек" |
Всего комментариев 59
Комментарии
-
а что делать если нужно непременно дождаться выполнения всех потоков?
Запись от qwertehok размещена 13.08.2017 в 22:02 -
А как можно НЕ дождаться? Программа всегда только и делает, что ждет какого то события.
Дождаться. Думаю, вы имеете в виду "не мочь чего-то сделать, пока не вернется последний поток".
Полный эквивалент этого выражения - "сделать что-то, когда вернется последний поток".
Как определить, что вернулся последний поток, здесь показано.
Кстати, на протяжении всей работы в этом примере недоступна кнопка Start. Способ отследить, идет еще работа или уже всё, тоже есть.
Все нужные механизмы на месте. Без привлечения дополнительных сущностей.Запись от krapotkin размещена 13.08.2017 в 22:07
Обновил(-а) krapotkin 13.08.2017 в 22:20 -
Запись от extrimportal размещена 14.08.2017 в 12:02 -
Цитата:Потоки не должны ничего читать и писать в формах и компонентах!!!
Если не делать поправки - то вы потеряете "интерактивность" приложения.
Ибо руководствуясь эти правилом при
Цитата:Самая стандартная задача - сходить в потоке в интернет и что-то скачать.
Цитата:Часто путают поток ОС с классом TThread
Это не одно и то же.
Правда что бы его использовать, во первых нужно знать что такое "классы" и что такое "потоки ОС".
Цитата:Delphi 1 2
FreeOnTerminate := true; OnTerminate := TermProc;
и не трогать руками после:
Delphi 1 2 3 4
FThread:= TMyThread.Create(Url, true); // CreateSuspended:= true FThread.FreeOnTerminate := true; FThread.OnTerminate := TermProc; FThread.Start(); // FThread.Resume()
Цитата:А если нужно остановить все потоки в один момент, что бы к примеру поменять настройки и запустить с начала?
Имею ввиду не получится "мягко" остановить, а "грубо"- не хорошо.
Т.е. нужно дать команду Terminate и ждать мягкого завершения.
Как дождаться полного их завершения - тут есть варианты, в зависимости от ситуации.Запись от Avazart размещена 14.08.2017 в 15:10
Обновил(-а) Avazart 14.08.2017 в 15:58 -
Цитата:
Если же вы делаете запрос в интернет или к БД, то можно только лишь игнорировать его результаты. Это будет вполне адекватным решением поставленной задачи "запустить с начала"Запись от krapotkin размещена 14.08.2017 в 16:27 -
Цитата:что бы его использовать, во первых нужно знать что такое "классы" и что такое "потоки ОС".
Не невозможно, но безыдейно.
По поводу свойств и конструктора - я предлагаю не более чем "good practice". Построена она на многолетнем опыте и собственной работы и особенно ответов на вопросы на этом форуме.
Всегда может найтись повод сделать по-другому.
В том числе, синхронизация, запиленная в Execute у 80% авторов бессмысленна (и беспощадна)))
Например прямо перед выходом из execute. Или весь execute в Synchronize
Или использование что-то типа IF Form1.CheckBox2.Checked вместо нормального флага приводит к кросс-зависимостям логики и UI. Поэтому простое слово "нельзя" вполне избавляет нас от такого дурацкого подхода...Запись от krapotkin размещена 14.08.2017 в 16:35 -
Цитата:По поводу свойств и конструктора - я предлагаю не более чем "good practice". Построена она на многолетнем опыте и собственной работы и особенно ответов на вопросы на этом форуме.
Класс потока все же класс, и стоит его настраивать извне, если возможно.
Гуд это "выставлять свойства до запуска потока и не трогать руками после"
Цитата:Например прямо перед выходом из execute. Или весь execute в Synchronize
Или использование что-то типа IF Form1.CheckBox2.Checked вместо нормального флага приводит к кросс-зависимостям логики и UI. Поэтому простое слово "нельзя" вполне избавляет нас от такого дурацкого подхода...
Как я сказал нужна нормальная синхронизация.
Вы сами привели реальный пример:
Цитата:Самая стандартная задача - сходить в потоке в интернет и что-то скачать.
Не такая уж сложная задача обновлять прогресс бар 1/раз за цикл или же в обработчике OnWork
(с должной синхронизацией)
P.S: Кроме Synchronize() есть и другие средства синхронизации, опять же Indy, да и примитивы самой ОС и их обвертки.Запись от Avazart размещена 14.08.2017 в 17:46
Обновил(-а) Avazart 14.08.2017 в 17:59 -
Цитата:Если же вы делаете запрос в интернет
Акцент на том что остановка не происходит моментально.
Более того стоит стараться строить свой код так что бы пользователь не ждал долго остановки, но и проверки
("точки выхода") не были частыми что бы не притормаживать поток.Запись от Avazart размещена 14.08.2017 в 18:03 -
Запись от DenNik размещена 18.08.2017 в 16:16 -
Уважаемый krapotkin, а можете ли дополнить статью, показать как передаете значения в потоки с вцл и обратно (едитов, чекбоксов, мемо) если их много.
И еще вы говорите что это плохо
"while true and Potoki<100 do Application.ProcessMessages;"
но как тогда взаимодействовать с формой пока потоки работают, что бы хотя бы элементарно передвинуть её в сторону.Запись от extrimportal размещена 18.08.2017 в 17:01
Обновил(-а) extrimportal 18.08.2017 в 17:09 -
если вы смотрели код, а еще лучше - запускали его, то вы увидите, что вся форма работает, перетаскивается и может делать все что ей положено.
по пересылке сообщений форме тут все сложно. с одной стороны, это выход, с другой, передача строк через сообщения связана с пониманием, что и как выделяется и уничтожается в памяти.
если честно, не так уж часто поток делает что-то, что РЕАЛЬНО нужно отобразить на форме.
в последнее время это простой гет, который не вернется пока не выполнится...
и в этом случае я обычно все-таки делаю Synchronize, ибо это проще. При этом ссылку на форму стараюсь все равно не давать, даю ссылку сразу на компонент
например
Delphi 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
th:=TMyThread.Create(...); th.progressBar := progressBar1; ... procedure TMyThread.ShowProgress; begin progressBar.position := FIndex div 1000; end; procedure TMyThread.Execute(); begin FIndex:=0; while FIndex < 100000 do begin if index mod 1000 = 0 then Synchronize(ShowProgress); .... end end;
Запись от krapotkin размещена 18.08.2017 в 19:54
Обновил(-а) krapotkin 18.08.2017 в 19:59 -
Запись от Avazart размещена 18.08.2017 в 20:43 -
Запись от krapotkin размещена 18.08.2017 в 20:51 -
Запись от Avazart размещена 19.08.2017 в 10:00
Обновил(-а) Avazart 19.08.2017 в 10:04 -
Запись от krapotkin размещена 20.08.2017 в 19:35 -
И что мне должна сказать эта ссылка?
Indy давно уже неотъемлемая часть делфей, если вы только сейчас проснулись и не в курсе.
Цитата:да еще и правда излишняя
Цитата:Самая стандартная задача - сходить в потоке в интернет и что-то скачать.Запись от Avazart размещена 20.08.2017 в 21:10 -
Запись от krapotkin размещена 20.08.2017 в 21:47 -
Запись от Avazart размещена 20.08.2017 в 23:43
Обновил(-а) Avazart 20.08.2017 в 23:46 -
Запись от DenNik размещена 22.08.2017 в 10:18 -
Тут тот же резон, почему я давно не пользуюсь запихиванием нужных данных компоненту в Tag и другими "быстрыми костыликами".
Сначала это работает, а потом всегда хочется чего-то еще. А таг один. И все равно переписывать, но теперь уже выискивая по коду, кто что в этом таге читает))
А так да, конечно быстро.
Вот только хэндлы у окон windows тоже меняются иногда )Запись от krapotkin размещена 22.08.2017 в 11:13