Блог. Двадцать пять лет Делфи-практики
В этом блоге я буду публиковать ответы на вопросы, которые постоянно приходится повторять на форуме.
Здесь можно это сделать более развернуто и спокойно.
Все, что здесь написано, не является истиной в последней инстанции, скорее, это результат моих размышлений над архитектурой проектов, маленьких и больших, которых я сделал на Делфи более дюжины.
Начав с Делфи-2 двадцать пять лет назад, я прошел все версии, испробовал массу технологий, включая работу с БД, с графикой DirectX, связью с серверами и интернетом, разработку на Андроид и IOS, и многое, многое другое.
________________________________________ ________________________________________ ____
P.S. все, о чем здесь написано, всего лишь измышления из головы.
совпадения с реальными людьми и фактами случайны.
В этом блоге я буду публиковать ответы на вопросы, которые постоянно приходится повторять на форуме.
Здесь можно это сделать более развернуто и спокойно.
Все, что здесь написано, не является истиной в последней инстанции, скорее, это результат моих размышлений над архитектурой проектов, маленьких и больших, которых я сделал на Делфи более дюжины.
Начав с Делфи-2 двадцать пять лет назад, я прошел все версии, испробовал массу технологий, включая работу с БД, с графикой DirectX, связью с серверами и интернетом, разработку на Андроид и IOS, и многое, многое другое.
________________________________________ ________________________________________ ____
P.S. все, о чем здесь написано, всего лишь измышления из головы.
совпадения с реальными людьми и фактами случайны.
Про потоки
Запись от krapotkin размещена 13.08.2017 в 10:03
Показов 32985
Комментарии 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
uses Classes, ComCtrls, Graphics, IdSync; type TProgressNotify= class(TIdNotify) private FProgress:TProgressBar; FValue:Int64; FStart:Boolean; protected procedure DoNotify;override; public constructor Create(AProgress:TProgressBar; AValue:Int64; AStart:Boolean=False); end; implementation procedure TProgressNotify.DoNotify; begin if FStart then FProgress.Max:= FValue else FProgress.Position:= FValue; end; constructor TProgressNotify.Create(AProgress:TProgressBar; AValue:Int64; AStart:Boolean=False); begin FProgress:= AProgress; FValue:= AValue; FStart:= AStart; end;
И аналогично для строк, для ведения лога в RichEdit c подсветкой цветом и выделением шрифта.Запись от Avazart размещена 22.08.2017 в 14:56
-
Статья хорошая, но не для всех классов задач. Для чисто вычислительных задач, когда нужно распределить промежуточные вычисления по процессорам, нужно именно ждать в основной ветке. Можно, конечно, и в блоке OnTerminate отлавливать завершение вычислений, и в нём продолжать дальнейшие вычисления, но при этом программа теряет читаемость и логику.Запись от Massaraksh7 размещена 24.08.2017 в 02:10
-
Запись от Avazart размещена 24.08.2017 в 12:08
-
Запись от krapotkin размещена 24.08.2017 в 13:25
-
Запись от Massaraksh7 размещена 24.08.2017 в 14:02
-
Запись от Massaraksh7 размещена 24.08.2017 в 16:36
-
Запись от Avazart размещена 24.08.2017 в 18:25
-
Запись от krapotkin размещена 24.08.2017 в 18:28
-
Пожалуйста.
Сообщение от krapotkin
Функция получает на входе число N. Считает сумму 0...4*N-1, распределяя вычисления по 4 процессам и возвращает результат. Вот так написал её я:
Ваша версия?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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
type TMyThread=class(TThread) private { Private declarations } protected procedure Execute; override; public startvalue:uint64; // начальное значение суммирования stopvalue:uint64; // конечное значение суммирования sum:uint64; // сумма-результат потока procedure Add; end; var Th:array [0..3] of TMyThread; h,hs:THandle; xsum:uint64; procedure TMyThread.Add; begin WaitForSingleObject(hs,INFINITE); xsum:=xsum+sum; ReleaseSemaphore(hs,1,nil); end; procedure TMyThread.Execute; var i:uint64; begin sum:=0; // сумма потока i:=startvalue; while (i<=stopvalue) do begin sum:=sum+i; i:=i+1; end; Add; ReleaseSemaphore(h,1,nil); end; function LSum(step:uint64):uint64; var i,k:integer; t:TDateTime; l:uint64; begin for i:=0 to 3 do begin Th[i]:=TMyThread.Create(True); Th[i].FreeOnTerminate:=True; l:=step;l:=l*i; Th[i].startvalue:=l; // Распределение начальной и конечной Th[i].stopvalue:=l+step-1; // сумм по потокам end; xsum:=0; // Общая сумма=0 h:=CreateSemaphore(nil,0,4,nil); //--Семафор для ожидания завершения потоков hs:=CreateSemaphore(nil,1,4,nil); //--Семафор потоков для доступа к общему ресурсу for i:=0 to 3 do Th[i].Start; // Запуск 4 потоков for i:=0 to 3 do WaitForSingleObject(h,INFINITE); // Ожидание завершения вычислений CloseHandle(h);CloseHandle(hs); Result:=xsum; end;
Запись от Massaraksh7 размещена 24.08.2017 в 19:20
-
Запись от Avazart размещена 24.08.2017 в 20:53
-
А как же BackgroundWorker c ним у меня получилось писать в форме)Запись от Алеск размещена 24.08.2017 в 21:54
-
Запись от Massaraksh7 размещена 24.08.2017 в 21:56
-
Запись от Avazart размещена 24.08.2017 в 23:06
-
Запись от Massaraksh7 размещена 24.08.2017 в 23:13
-
Запись от Avazart размещена 24.08.2017 в 23:19
-
Запись от Massaraksh7 размещена 24.08.2017 в 23:26
-
в данном коде есть точный и конкретный признак излишнего/неправильного/неоптимального -
это выполнение синхронизации в конце Execute.НахренаЗачем делать синхронизацию, если и так вот вот она сама выполнится по факту???
Ни один семафор в процессе изготовления не пострадал. Вообще ни одного объекта синхронизации не было использовано.
При этом программа отвечает на все запросы, и даже пишет что-то в мемо.
Кликните здесь для просмотра всего текста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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
unit UMainForm; interface uses classes, types, forms, Controls, StdCtrls, Dialogs, SysUtils, ExtCtrls; type TMainForm = class(TForm) bStart: TButton; m1: TMemo; tmr1: TTimer; procedure bStartClick(Sender: TObject); procedure tmr1Timer(Sender: TObject); private FSUM : Int64; FFinished:integer; procedure Log(const s: string); procedure ThreadTerm(Sender: TObject); public procedure RunThread(AID:integer); end; var MainForm: TMainForm; const INTERVAL=65536; THREAD_COUNT=4; implementation uses UMyThread; {$R *.dfm} { TMainForm } procedure TMainForm.Log(const s:string); begin m1.Lines.add(TimeToStr(Now)+' '+s ); end; procedure TMainForm.ThreadTerm(Sender: TObject); var TH:TMyThread absolute Sender; begin log(Format('Поток %d завершен с промежуточным результатом %d',[th.FID,th.Result])); FSUM := FSUM + th.Result; inc(FFinished); if FFinished=THREAD_COUNT then begin log('=========================='); log(Format('Работа окончена. Итог: %d',[FSum])); Log(''); Log(''); bStart.Enabled := true; tmr1.Enabled := false; end; end; procedure TMainForm.bStartClick(Sender: TObject); var I: Integer; begin bStart.Enabled := false; FSUM := 0; FFinished := 0; tmr1.Tag := 0; tmr1.Enabled := True; Log('Начало работы'); for I := 0 to THREAD_COUNT-1 do RunThread(i); end; procedure TMainForm.RunThread(AID:integer); var th:TMyThread; begin th:=TMyThread.Create(AID, INTERVAL div THREAD_COUNT * AID, INTERVAL div THREAD_COUNT * (AID+1)-1, ThreadTerm); th.Resume; end; procedure TMainForm.tmr1Timer(Sender: TObject); begin Log(Format('Прошло %d сек',[tmr1.Tag])); tmr1.Tag := tmr1.Tag + 1; end; 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 34 35 36 37 38 39 40 41 42
unit UMyThread; interface uses Classes; type TMyThread = class(TThread) FID:integer; FFirst, FLast:int64; Result : int64; constructor Create(AID:integer; AFirst,ALast:int64; ATermProc:TNotifyEvent); procedure Execute; override; end; implementation { TMyThread } constructor TMyThread.Create(AID:Integer; AFirst,ALast:int64; ATermProc:TNotifyEvent); begin inherited Create(True); FID := AID; FFirst := AFirst; FLast := ALast; FreeOnTerminate := true; OnTerminate := ATermProc; end; procedure TMyThread.Execute; var i:int64; begin result := 0; i:=FFirst; while i<=FLast do begin result := result + i; Sleep(Random(3)); Inc(i); end; end; end.
Запись от krapotkin размещена 25.08.2017 в 07:32
-
Запись от Massaraksh7 размещена 25.08.2017 в 12:03
-
Запись от Avazart размещена 25.08.2017 в 12:05
-
Запись от Massaraksh7 размещена 25.08.2017 в 12:09


