![]() |
|
|
|||||||
| Регистрация | << Правила форума >> | FAQ | Пользователи | Календарь | Поиск | Сообщения за сегодня | Все разделы прочитаны |
![]() |
|
|
Опции темы | Поиск в этой теме | Опции просмотра |
|
|
|
#1
|
||||
|
||||
|
Собственно была программа на D7, ввиду запутанности кода и в связи с переходом на XE4 решил переписать с нуля.
В частности, из главного окна, в цикле запускаются потоки, в потоке IdHTTP проверяет наличие файла и возвращает строку в зависимости от ответа. Когда наследник TThread описан в модуле главного окна все просто Код:
...
Synchronize(WriteRespons);
end;
procedure TDownload.WriteRespons;
begin Form1.Memo1.Lines.Append(ResStr);
Form1.ProgressBar2.Position:= trcount;
{и т.д.}Вроде бы вариант - посылка сообщения, но в сообщении нельзя передать строку, можно передать указатель, а с указателями для меня не все ясно. Где-то наткнулся на идею обработать OnTerminate Код:
unit MyThreads;
...
type
TMyThread = class(TThread)
...
public
property Str: string read FStr write FStr;
end;
unit Unit1;
...
var
Form1: TForm1;
thr: TMyThread;
...
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
begin
ListBox1.Clear;
for i := 0 to 12 do
begin
thr:= TMyThread.Create(True);
thr.FreeOnTerminate:= True;
thr.OnTerminate:= Form1.MyProc;
thr.Start;
end;
end;
procedure TForm1.MyProc(Sender: TObject);
begin
ListBox1.Items.Append('Str - '+thr.Str);
end;Вообще в Дельфи стандартно и создается отдельный модуль (New>Other>Thread Object) и в заготовке модуля в комментарии говорится о необходимости использования Synchronize, значит есть какой то способ обмена данными. А во всех статьях "Потоки для чайников" пишут - " ... для простоты опишем новый класс в главном модуле ...", хоть кто-нибудь привел бы пример с отдельным unit-ом. Похоже я как всегда упускаю что-то до такой степени очевидное, что об этом ни кто не пишет. |
|
#2
|
|||
|
|||
|
Да вариантов масса.
Кстати, отправлять через сообщения можно все-что угодно, в т.ч. целые структуры. Там только один момент. Если надо отправлять после полной сметри потока (т.е. перед ней, затем поток умирает, а когда произойдет обработка переданной инфы - фиг знает), то надо выделять память для этого в куче (ну и желательно блокировть ее). Теперь по синхронизации. Synchronize никто не отменял (это для синхронизации с главным тредом). Если у тебя потоков много, то придется еще и синхронизировать их между собой через критические секции. Вот маленький пример с использованием делегата (это шаблон проектирования такой): Поток: Код:
unit Unit2;
interface
uses
System.Classes;
type
TCallMainFormEvent = procedure (AMsg : String) of object;
TWorkerThread = class(TThread)
private
{ Private declarations }
FCallBack : TCallMainFormEvent;
FMsg : String;
FID : Integer;
procedure CallMainForm;
protected
procedure Execute; override;
public
constructor Create(CreateSuspned : Boolean; CallBack : TCallMainFormEvent);
end;
implementation
{
Important: Methods and properties of objects in visual components can only be
used in a method called using Synchronize, for example,
Synchronize(UpdateCaption);
and UpdateCaption could look like,
procedure TWorkerThread.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end;
or
Synchronize(
procedure
begin
Form1.Caption := 'Updated in thread via an anonymous method'
end
)
);
where an anonymous method is passed.
Similarly, the developer can call the Queue method with similar parameters as
above, instead passing another TThread class as the first parameter, putting
the calling thread in a queue with the other thread.
}
{ TWorkerThread }
uses
System.SysUtils, SyncObjs;
var
ThreadSync : TCriticalSection;
constructor TWorkerThread.Create(CreateSuspned : Boolean; CallBack : TCallMainFormEvent);
begin
FCallBack := CallBack;
FMsg := ''; // Just in case
FID := Random(2000000000); // Just generate random ID for each thread
inherited Create(CreateSuspned);
end;
procedure TWorkerThread.Execute;
var
thrdDelay : Integer;
begin
FMsg := 'Thread started.';
Synchronize(CallMainForm);
thrdDelay := Random(5000); // random delay 0 - 5 second
Fmsg := Format('Thread delay set to %d miliseconds.',[thrdDelay]);
Synchronize(CallMainForm);
Sleep(thrdDelay);
FMsg := 'Thread finished.';
Synchronize(CallMainForm);
end;
procedure TWorkerThread.CallMainForm;
begin
ThreadSync.Enter;
Try
If Assigned(FCallBack) Then FCallBack(Format('Thread #%d: %s',[FID,FMsg]));
Finally
ThreadSync.Leave;
End;
end;
initialization
ThreadSync := TCriticalSection.Create;
finalization
ThreadSync.Free;
end.Код:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TMainForm = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
procedure ThreadCallBack(Msg : String);
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
uses Unit2;
procedure TMainForm.Button1Click(Sender: TObject);
const
thrdNmb : Integer = 10; // Number of threads to create
var
I : Integer;
Thrd : TWorkerThread;
begin
Memo1.Lines.Clear;
For I := 1 To thrdNmb Do
Begin
Thrd := TWorkerThread.Create(True,ThreadCallBack);
Thrd.FreeOnTerminate := True;
Thrd.Resume;
End;
end;
procedure TMainForm.ThreadCallBack(Msg : String);
begin
Memo1.Lines.Add(Msg);
end;
end.Код проверен в D10.2.3 Berlin. Последний раз редактировалось lmikle, 13.12.2018 в 06:26. |
| Этот пользователь сказал Спасибо lmikle за это полезное сообщение: | ||
Помидоркин (13.12.2018)
| ||
|
#3
|
||||
|
||||
|
Огромное спасибо, в целом все понятно, есть пара вопросов -
Код:
procedure TWorkerThread.Execute;
begin
....
Synchronize(CallMainForm);
end;
procedure TWorkerThread.CallMainForm;
begin
ThreadSync.Enter;
Try
If Assigned(FCallBack) Then FCallBack(Format('Thread #%d: %s',[FID,FMsg]));
Finally
ThreadSync.Leave;
End;
end;Во всех примерах, которые мне попадались, Synchronize и CriticalSection фигурируют как разные способы синхронизации. Код:
procedure TUrlChek.Execute;
begin
....
cntlock.Enter;
trcount:= trcount-1;
cntlock.Leave;
Synchronize(AddGall);
end;
procedure TUrlChek.AddGall;
begin
....
второе: нужно что бы поток проверял наличие папки и при отсутствии создавал ее, не может ли здесь произойти конфликт? И если можно еще два вопроса не совсем по теме: 1. правильно ли я понимаю - в цикле мы создаем потоки Код:
Thrd := WorkerThread.Create(True,ThreadCallBack); Код:
Thrd.FreeOnTerminate := True; // и т.д. 2. в потомке мы перегружаем конструктор Код:
constructor Create(CreateSuspned : Boolean; CallBack : TCallMainFormEvent); Код:
constructor TWorkerThread.Create(CallBack : TCallMainFormEvent); begin FCallBack := CallBack; .... inherited Create(True); end; Последний раз редактировалось Помидоркин, 14.12.2018 в 14:11. |
|
#4
|
|||
|
|||
|
1. потому что Synchronize - это синхронизация с главным потоком. Однако, потоки надо еще между собой синхронизировать. Короче, это не просто так сделано. Если бы был один поток, то CriticalSection не нужна. Т.к. потоков много, то надо их еще между собой синхронизировать.
2. При проверке/создании папки конфликн, конечно, может произойти. Используй критическую секцию. 3. Да, понимаешь правильно. 4. Нет, override там не нужен. В принципе, там лучше указать reintroduce, просто не знаю твою версию Дельфи, так что решил не использовать. 5. Ну, можно и убать параметр, тогда вообще писать конструктор не надо, тогда передавай ссылку на колбэк через свойства. |
| Этот пользователь сказал Спасибо lmikle за это полезное сообщение: | ||
Помидоркин (16.12.2018)
| ||
|
#5
|
||||
|
||||
|
Возникла новая проблема.
Собственно все работает как и должно работать, но... Суть в следующем - потоки запускаются в цикле, их количество может быть несколько тысяч, ограничиваю число одновременно работающих Код:
unit Unit1;
...
var
Form1: TForm1;
ThrdCount: Byte;
implementation
...
procedure TForm1.btnCheckClick(Sender: TObject);
var Checker: TUrlCheckThrd;
Indx: Integer;
begin
for Indx := First to Last do
begin
Checker:= TUrlCheckThrd.Create;
...
Inc(ThrdCount);
while ThrdCount>7 do Sleep(500); //тут окно и зависает
end;
end;Решил создать поток, который будет запускать остальные потоки, так сказать "родительский", эти остальные будут посылать сообщение о своем завершении, Код:
unit MyThread; ..... procedure TUrlCheckThrd.Execute; begin .... Synchronize(CallMainForm); SendMessage(FWND,DEC_THRDCOUNT,0,0); end; Код:
unit MainThread;
..........
type
TMainThread = class(TThread)
private
FThrdCount: Byte;
...........
procedure SetThrdCount(var Msg: TMessage); message DEC_THRDCOUNT;
protected
procedure Execute; override;
.............
end;
implementation
procedure TMainThread.SetThrdCount(var Msg: TMessage);
begin
Dec(FThrdCount);
end;
procedure TMainThread.Execute;
var Checker: TUrlCheckThrd;
Indx: Integer;
begin
for Indx := FFirst to FLast do
begin
Checker:= TUrlCheckThrd.Create(FCallForm, Self.Handle); // дочерний поток получает хэндл родительского
...............
Checker.Start;
Inc(FThrdCount);
while FThrdCount>7 do Sleep(500);
end;
end; |
|
#6
|
|||
|
|||
|
Ну, для начала, поток не может получать стандартные ОКОННЫЕ сообщения, бо как окна то и нету. Вроде есть функция ThreadPostMessage, но я ею никогда не пользовался. Подозреваю, что там еще и поток-получатель надо где-то регистрировать.
Теперь по сути. Изначально неправильная архитектура. Если предполагается, что заданий для обработки может быть много, ОЧЕНЬ МНОГО, то приложение строится немного по другому. Есть такие понятия как очередь заданий и пул потоков. Очередь заданий - синглтон объект (объект, существующий в единственном экземпляре), куда добавляются задания для последуюший ообработки, в твоем случае - урлы. Пул потоков - объект, реализующий управление заданным кол-вом потоков. Как это работает. Главный поток, или несколько других потоков, добавляют в очередь задания (кстати, любая операция с очередью должна быть синхронизированна через CriticalSection). Далее есть некоторый пул потоков (не важно как это реализованно, можно, например, сразу при старте программы запустить десяток потоков, которые просто будут ждать заданий, а можно запускать потоки по мере надобности, но контролировать их кол-во). Каждый поток крутит цикл внутри Execute - проверяем есть ли задания в очереди, если есть - то берем первое и начинаем выполнять, если нет - то засыпаем, например, на 5 сек. Пример кода нада? |
| Этот пользователь сказал Спасибо lmikle за это полезное сообщение: | ||
Помидоркин (19.12.2018)
| ||