[Lazarus] Wycieki pamięci
: środa 22 sty 2020, 12:04
Próbuję stworzyć aplikację w Lazarusie. Jej głównym zadaniem jest komunikacja po RS485 (protokół ModBUS). Używam do tego biblioteki PascalSCADA (do pobrania stąd).
Fragmenty programu:
Najpierw tworzę klasę, pochodną (dziedziczną?) po klasie TThread, która zawiera elementy do komunikacji:
Następnie powołuję zmienną:
Na formularzu mam jeszcze timer, który ma ustawiony interval na 500 ms, i w obsłudze OnTimer ma procedurę:
Procedura ta ma na celu całkowite usunięcie wątku z pamięci komputera. I tu jest problem, ale o problemie za chwilę. Najpierw dokończę opis programu.
Procedury
zawierają tylko jedną linijkę (na razie):
Jest jeszcze jeden timer, który co 200 ms uruchamia timer1 (ten, który ma za zadanie usunąć wątek) i jeszcze jeden timer, który co 100 ms realizuje procedurę (czyli, jeżeli nie ma aktywnego wątku, to go tworzy):
Mam nadzieję, że nie pominąłem czegoś ważnego.
I teraz opis problemu:
Ponieważ biblioteka PascalSCADA nie ma zaimplementowanej obsługi timeoutu przy "zniknięciu" wirtualnego portu COM, więc aplikacja "wisi". Stąd pomysł na komunikację w nowym wątku, ponieważ wątek ten mogę zniszczyć jak będzie "wisiał".
A problem jest taki, że z czasem zwiększa się ilość pamięci używana przez aplikację.
Uruchomiłem moduł heapTRC, z którego wynika, że wyciek pamięci jest gdzieś przy okazji wątków.
Podejrzewam, że nie usuwam nieaktywnego wątku z pamięci.
Ktoś wie, jak to zrobić poprawnie?
Wstawiam jeszcze pozostałe procedury z klasy:
Fragmenty programu:
Najpierw tworzę klasę, pochodną (dziedziczną?) po klasie TThread, która zawiera elementy do komunikacji:
Code: Select all
TKomunikacja = class(TThread)
ModBusRTUDriver1: TModBusRTUDriver;
PLCBlock1: TPLCBlock;
SerialPortDriver1: TSerialPortDriver;
procedure PLCBlock1Update(Sender: TObject);
procedure SerialPortDriver1CommErrorReading(Error: TIOResult);
private
public
Constructor Create(CreateSuspended : boolean);
destructor Destroy(); override;
protected
procedure Execute; override;
end;
Code: Select all
WatekKomunikacja: TKomunikacja;
Code: Select all
procedure TForm1.Timer1Timer(Sender: TObject);
begin
WatekKomunikacja.Terminate;
WatekKomunikacja := NiL;
end;
Procedury
Code: Select all
procedure PLCBlock1Update(Sender: TObject);
procedure SerialPortDriver1CommErrorReading(Error: TIOResult);
Code: Select all
Form1.Timer1.Enabled := False;
Code: Select all
procedure TForm2.Timer1Timer(Sender: TObject);
begin
If not Assigned(WatekKomunikacja) Then WatekKomunikacja := TKomunikacja.Create(False);
end;
I teraz opis problemu:
Ponieważ biblioteka PascalSCADA nie ma zaimplementowanej obsługi timeoutu przy "zniknięciu" wirtualnego portu COM, więc aplikacja "wisi". Stąd pomysł na komunikację w nowym wątku, ponieważ wątek ten mogę zniszczyć jak będzie "wisiał".
A problem jest taki, że z czasem zwiększa się ilość pamięci używana przez aplikację.
Uruchomiłem moduł heapTRC, z którego wynika, że wyciek pamięci jest gdzieś przy okazji wątków.
Podejrzewam, że nie usuwam nieaktywnego wątku z pamięci.
Ktoś wie, jak to zrobić poprawnie?
Wstawiam jeszcze pozostałe procedury z klasy:
Code: Select all
constructor TKomunikacja.Create(CreateSuspended: boolean);
begin
FreeOnTerminate := False;
inherited Create(CreateSuspended);
SerialPortDriver1 := TSerialPortDriver.Create(NiL);
SerialPortDriver1.Active := False;
SerialPortDriver1.OnCommErrorReading := @SerialPortDriver1CommErrorReading;
SerialPortDriver1.COMPort := '';
ModBusRTUDriver1 := TModBusRTUDriver.Create(NiL);
ModBusRTUDriver1.CommunicationPort := SerialPortDriver1;
PLCBlock1 := TPLCBlock.Create(NiL);
PLCBlock1.AutoRead := True;
PLCBlock1.AutoWrite := False;
PLCBlock1.MemAddress := 4000;
PLCBlock1.MemReadFunction := 3;
PLCBlock1.MemWriteFunction := 16;
PLCBlock1.PLCStation := 1;
PLCBlock1.ProtocolDriver := ModBusRTUDriver1;
PLCBlock1.OnUpdate := @PLCBlock1Update;
end;
destructor TKomunikacja.Destroy();
begin
inherited Destroy;
inherited Free;
end;
procedure TKomunikacja.Execute;
const
i : Integer = 0;
var
portyCOM : TMemo;
port : String;
znaleziony : Boolean = False;
listaPortow : String;
begin
portyCOM := TMemo.Create(NiL);
While Not Terminated Do
begin
repeat
Form1.Timer1.Enabled := False;
listaPortow:=GetSerialPortNames;
ExtractStrings([','], [], PChar(listaPortow), portyCOM.Lines );
For port in portyCOM.Lines Do
begin
If port = 'COM48' Then znaleziony := True;
end;
sleep(1);
until znaleziony;
PLCBlock1.AutoRead := True;
If Not SerialPortDriver1.Active Then
begin
SerialPortDriver1.COMPort := 'COM48';
SerialPortDriver1.Active:=True;
end;
PLCBlock1.Read;
i := i + 1;
If i = 10 Then
begin
i := 0;
z := z + 1;
end;
Sleep(1);
end;
Destroy();
end;