1:12 AM TThread: с чем его едят (часть 2) |
Начало здесь 5. TThread: передача простых типов туда и обратно
5. TThread: передача простых типов туда и обратно.
{ TMyThread } TMyThread = class(TThread) private FStr1, FStr2: String; FInt1, FInt2: Integer; protected procedure Execute; override; public constructor Create (CreateSuspended: boolean; var AStr1, AStr2: String; var AInt1,AInt2: Integer); end;
constructor TMyThread.Create(CreateSuspended: boolean; var AStr1, AStr2: String; var AInt1,AInt2: Integer); begin <skiped> FStr1:= AStr1; FStr2:= AStr2; FInt1:= AInt1; FInt2:= AInt2; <skiped> end;
TMyThread = class(TThread) private //массивы-поля FStrArr: Array of String; FIntArr: Array of Integer;
constructor TMyThread.Create(CreateSuspended: boolean; AStrArr: Array of String; AIntArr: Array of Integer {или AConst: Array of const}); var i: Integer; begin <skiped> if Length(AStrArr) > 0 then //если длина массива-параметра ненулевая begin SetLength(AStrArr,Length(AStrArr));//устанавливаем длину массива-поля for i := Low(AStrArr) to High(AStrArr) do //инициализируем поля массива-поля AStrArr[i]:= AStrArr[i]; end; //то же самое делаем для массива FIntArr <skiped> end;
type TMyDataRec = packed record AInt: Integer; AStr: String; AVar: Variant; end; TMyDataRecArr: array of TMyDataRec; { TMyThread } TMyThread = class(TThread) private FArrDataRec: array of TMyDataRec; protected procedure Execute; override; public constructor Create (CreateSuspended: boolean; ArrDataRec: array of TMyDataRec); //или даже так //constructor Create (CreateSuspended: boolean; ADataRecArr: TMyDataRecArr); end;
constructor TMyThread.Create(CreateSuspended: boolean; ArrDataRec: array of TMyDataRec); var i: Integer; begin <skiped> if Length(ArrDataRec) > 0 then begin SetLength(FArrDataRec,Length(ArrDataRec)); for i := Low(ArrDataRec) to High(ArrDataRec) do begin FArrDataRec[i].AInt:= ArrDataRec[i].AInt; FArrDataRec[i].AStr:= ArrDataRec[i].AStr; FArrDataRec[i].AVar:= ArrDataRec[i].AVar; end; end; <skiped> end;
var Msg: TLMessage; begin Msg.wParam:= -123; //Msg.wParam:= NativeInt(-123);// можно так Msg.lParam:= 456; SendMessage(Form1.Handle,WM_INT_MSG,Msg.wParam,Msg.lParam); ... end; procedure TForm1.WM_Int_Msg(var Msg: TLMessage); begin //оба способа корректны Label1.Caption:= IntToStr(NativeInt(Msg.wParam)) ; Label2.Caption:= IntToStr(Msg.lParam) ; end; Строки тоже передаются, как целое или указатель. Поэтому способ передачи сводится к выбору способа их преобразования к целому и обратно (один из способов я уже упомянул выше ) procedure TMyThread.Execute; var Msg: TLMessage; s1,s2: String; begin s1:= 'Яблоко'; s2:= 'Apple'; Msg.wParam:= NativeInt(s1);//тип LongInt только для x32 {1} Msg.lParam:= IntPtr(PChar(s2));{2} //еще один способ SendMessage(Form1.Handle,WM_STR_MSG,Msg.wParam,Msg.lParam); ... end; procedure TForm1.WM_Str_Msg(var Msg: TLMessage); begin Label1.Caption:= String(Msg.wParam) ;{1} Label2.SetTextBuf(PChar(Msg.lParam));{2} end; Update (13.04.2019): для программирующих на FPC/Lazarus настоятельно рекомендуется вместо NativeInt/NativeUInt использовать соответственно IntPtr/UIntPtr. В справке к fpc это обосновывается так: "NativeInt и NativeUInt являются типами совместимости c Delphi. Хотя Delphi [также] имеет [типы] IntPtr и UIntPtr, документация Delphi для NativeInt утверждает, что 'размер NativeInt эквивалентен размеру указателя для текущей платформы'. Из-за вводящих в заблуждение имен эти типы не должны использоваться RTL FPC. Обратите внимание, что на i8086 их размер изменяется между 16-битными и 32-битными [значениями] в зависимости от модели памяти, так что они на самом деле там вообще не тип int." procedure TMyThread.Execute; var Msg: TLMessage; s1: String; begin s1:= 'Яблоко'; PostMessage(Form1.Handle,WM_STR_MSG,WParam(s1),Msg.lParam); Pointer(s1):= nil; ... end; procedure TForm1.WM_Str_Msg(var Msg: TLMessage); var s1: String; begin s1:= ''; WPARAM(s1):= Msg.wParam; Label1.Caption:= S1; end;
Ну и пересылка записи(структуры) ... end;
6. TThread: использование "встроенных" методов Synchronize и Queue.
type
{ TMyThread }
TMyThread = class(TThread)
private
FLblMsg,
FBtnMsg: Integer;
procedure SendMsg2Label;
procedure SendMsg2Button;
protected
procedure Execute; override;
public
constructor Create(CreateSuspended: Boolean);
end;
procedure TMyThread.SendMsg2Label;
begin
FrmMain.Label1.Caption:= Format('Текущее значение счетчика: %d',[FLblMsg]);
end;
procedure TMyThread.SendMsg2Button;
begin
FrmMain.Button1.Caption:= IntToStr(FBtnMsg);
end;
procedure TMyThread.Execute;
const
k: Integer = 100;
var
i: Integer;
begin
for i:= 0 to k do
begin
FLblMsg:= i;//присвоим текущее значение счетчика
Queue(@SendMsg2Label);//аналог PostMessage
FBtnMsg:= k - i;//присвоим оставшееся значение счетчика
Synchronize(@SendMsg2Button);//аналог SendMessage
Sleep(50);
end;
end;
7. TThread: передача бинарных данных туда и обратно.
1) передача с использованием массива байтов
type
TStreamRec = packed record
ContainerType: Integer;//тип блоба(большой текст или картинка) - скорее, формальный параметр
ByteArray: array of Byte;//массив байтов
end;
var ms: TMemoryStream; //поток для Picture MyStreamRec: TStreamRec; begin if Assigned(FMyThread) then Exit; ms:= TMemoryStream.Create;//для картинки try ImgSrc.Picture.SaveToStream(ms); ms.Position:= 0; SetLength(MyStreamRec.ByteArray, ms.Size);//определяем размер массива байтов поля TStreamRec ms.Read(MyStreamRec.ByteArray[0],ms.Size);//пишем содержимое потока в массив поля записи MyStreamArr.ContainerType:= 1;//тип контейнера TImage //создаем поток с параметрами и ждем результата FMyThread:= TMyThread.Create(True,MyStreamRec); try while not FMyThread.FIsTermThread do begin Application.ProcessMessages; Sleep(500); end; finally FreeAndNil(FMyThread); end; finally FreeAndNil(ms);//теперь, после уничтожения доп.потока, можно освободить ресурсы end; end; Сам поток { TMyThread } TMyThread = class(TThread) private FStreamRec: TStreamRec; protected procedure Execute; override; public constructor Create (CreateSuspended: boolean; AStreamRec: TStreamRec); end; Копируем данные во внутренние структуры в конструкторе доп.потока
constructor TMyThread.Create(CreateSuspended: boolean; AStreamRec: TStreamRec);
begin
...
FStreamRec.ContainerType:= AStreamRec.ContainerType;
SetLength(FStreamRec.ByteArray,Length(AStreamRec.ByteArray));//определяем размер массива байтов
FStreamRec.ByteArray:= AStreamRec.ByteArray;
...
end;
Сохраняем данные внутри доп. потока, например, в БД
procedure TMyThread.Execute;
var
ms: TMemoryStream;
begin
ms:= TMemoryStream.Create;
try
ms.Write(FStreamRec.ByteArray[0],Length(FStreamRec.ByteArray));//записываем в MemoryStream содержимое массива байтов
ms.Position:= soFromBeginning;//сдвигаемся к началу потока
TBlobField(Table1.FieldByName('Picture')).LoadFromStream(ms);//сохраняем картинку в БД
finally
FreeAndNil(ms);
end;
end;
Передача блобов из доп.потока в основной поток
procedure TMyThread.Execute;
var
ms: TMemoryStream;
begin
ms:= TMemoryStream.Create;
try
//загружаем в MemoryStream бинарные данные из файла или блоб-поля БД
//копируем MemoryStream в массив байтов способом, аналогичным выше описанному
...
//отсылаем "целочисленный" указатель структуры в основной поток
SendMessage(Form1.Handle,WM_ADDSTREAMPARAM_MSG,0;LPARAM(@FStreamRec));
finally
FreeAndNil(ms);
end;
end;
В основном потоке получаем данные
procedure TForm1.WM_AddStreamParam_Msg(var Msg: TLMessage);
var
ARec: TStreamRec;
ms: TMemoryStream;
begin
ARec:= TStreamRec(Pointer(Msg.lParam)^);
ms:= TMemoryStream.Create;
try
ms.Write(ARec.ByteArray[0],Length(ARec.ByteArray));
ms.Position:= soFromBeginning;
ImgDest.Picture.LoadFromStream(ms);
finally
FreeAndNil(ms);
end;
end;
2) передача с использованием указателей
PStreamPtr = ^TMemoryStream
TStreamRec = packed record ContainerType: Integer;//тип блоба(большой текст или картинка) - скорее, формальный параметр StreamPtr: PStreamPtr;//указатель на MemoryStream end; Теперь загружаем и передаем бинарные данные в доп.поток так procedure TForm1.BtnThreadClick(Sender: TObject); var ms: TMemoryStream; //поток для Picture MyStreamRec: TStreamRec; begin if Assigned(FMyThread) then Exit; ms:= TMemoryStream.Create;//для картинки try ImgSrc.Picture.SaveToStream(ms); ms.Position:= 0; MyStreamArr.ContainerType:= 1;//тип контейнера TImage MyStreamArr.StreamPtr:= @ms;//записываем в поле указатель на MemoryStream //создаем поток с параметрами и ждем результата FMyThread:= TMyThread.Create(True,MyStreamRec); try // ждем завершения работы доп.потока finally FreeAndNil(FMyThread); end; finally FreeAndNil(ms);//освобождаем ресурсы только после уничтожения доп.потока!!! end; end; В конструкторе потока инициализируем приватные поля-переменные constructor TMyThread.Create(CreateSuspended: boolean; AStreamRec: TStreamRec); begin ... FStreamRec.ContainerType:= AStreamRec.ContainerType; FStreamRec.StreamPtr:= AStreamRec.StreamPtr; ... end; Сохраняем бинарные данные внутри доп.потока, например, в БД procedure TMyThread.Execute; var ms: TMemoryStream; begin ms:= TMemoryStream.Create; try ms.Write(FStreamRec.StreamPtr^,Length(FStreamRec.StreamPtr^));//записываем в MemoryStream содержимое массива байтов ms.Position:= soFromBeginning;//сдвигаемся к началу потока TBlobField(Table1.FieldByName('Picture')).LoadFromStream(ms);//сохраняем картинку в БД finally FreeAndNil(ms); end; end; Обратный процесс пересылки бинарных данных из доп.потока в основной поток можно организовать либо всю структуру, аналогично способу описанному здесь, либо передать только указатель на поле структуры procedure TMyThread.Execute; var ms: TMemoryStream; begin ms:= TMemoryStream.Create; try //загружаем в MemoryStream бинарные данные из файла или блоб-поля БД //копируем MemoryStream в массив байтов способом, аналогичным выше описанному ... //отсылаем "целочисленный" указатель структуры в основной поток
SendMessage(Form1.Handle,WM_ADDSTREAMPARAM_MSG,1;LPARAM(@FStreamRec.StreamPtr^));
finallyFreeAndNil(ms); end; end; В основном потоке разыменовываем указатель и пишем бинарные данные в контейнер procedure TForm1.WM_AddStreamParam_Msg(var Msg: TLMessage); begin case Msg.wParam of 0: MemoDest.Lines.LoadFromStream(TStream(Msg.lParam)); 1: ImgDest.Picture.LoadFromStream(TStream(Msg.lParam)); end; end; К преимуществу данного способа следует отнести относительно малое количество кода. Но есть один недостаток: объект, на который ссылается указатель, не должен уничтожаться до тех пор, пока в нем не отпадет окончательная надобность. Иначе можно получить AV. Поэтому при пересылке сообщений посредством функции PostMessage предпочтительно использовать первый способ. 8. TThread: универсализм применения.И напоследок. Можно написать кучу наследников TThread со своим набором полей, параметров конструктора и реализацией методов. Но все это загромождает код и затрудняет его отладку. Для себя я выбрал такой способ сделать наследника TThread более-менее универсальным. Я объявляю еще один тип, который будет определять, какая часть кода внутри Execute будет выполняться, и передаю его в качестве параметра в конструктор доп.потока type //вариант сценария работы дополнительного потока TMyThreadTarget = (mttConnectDB, mttDisconnectDB, mttOpenDSet {и т.д.}); { TMyThread } TMyThread = class(TThread) private FThreadTarget: TMyThreadTarget;//свойство, определяющее, какую часть кода в потоке задействовать protected procedure Execute; override; public constructor Create(CreateSuspended: Boolean; {здесь любой набор параметров;} AThreadTarget: TMyThreadTarget); end; constructor TMyThread.Create(CreateSuspended: boolean; AThreadTarget: TMyThreadTarget); begin ... FThreadTarget:= AThreadTarget; ... end; В самом методе Execute при помощи конструкции case..of реализуется нужная часть кода.
procedure TMyThread.Execute;
begin
case FThreadTarget of
mttConnectDB: ...
mttDisconnectDB: ...
mttOpenDSet: ...
end;
end;
Вот и все, что мне известно обо всем этом на данный момент :) |
|
Всего комментариев: 0 | |
| |