|
Antwort |
Registriert seit: 29. Dez 2006 Ort: NRW 831 Beiträge Delphi 10.4 Sydney |
#1
Datenbank: MySQL • Version: 5 • Zugriff über: FireDac
Hallo Zusammen,
ich entwickle meine erste MultiThread Client-Server App und kämpfe mit Fehlermeldungen, die ich leider nicht einsortieren kann und zu denen ich auch keine weiterführende Hilfe / Posts gefunden habe... Es werden von 10 Maschinen der aktuelle Status aus einer Datenbank abgefragt. Für jede Abfrage wird in der ClientApp ein eigener Thread der die Daten seiner Maschine vom Server abfragt.aufgemacht. Das funktioniert fehlerfrei wenn ich die serverseitige Datenbankabfrage weglasse. Wenn ich clientseitig eine einzelne Maschine abfrage, funktioniert es auch ohne Probleme, nur wenn ich alle 10 Maschinen gleichzeitig abfrage, kommt es zu der Fehlermeldung:
Zitat:
Erste Gelegenheit für Exception bei $7518F932. Exception-Klasse EFDException mit Meldung '[FireDAC][DatS]-1. Name [View] ist in der Liste doppelt vorhanden'. Prozess SrvrApp.exe (16920)
Die aufrufende Procedure erstellt vorab ein Object vom Typ TMxSQL, sodass es dort zu keiner Kollision kommen kann. Der Fehler scheint beim Öffnen der Query in dieser Funktion zu entstehen:
Delphi-Quellcode:
Aber ich vermute, dass es sich um ein generelles Problem handelt und nichts wirklich mit dieser Funktion zu tun hat.
procedure TMxSQL.Get_Settings(var Cols: TCols; var Rows: TRows; Tabelle: string; AStream: TStream);
var Logic: TLogic; qry_Settings: TFDQuery; begin Logic:= TLogic.Create; Logic.Set_Query_FDMngr(qry_Settings, 'BDHMySQL'); Try qry_Settings.sql.Add('select * from ' + Tabelle); ExecQuery(qry_Settings, Cols, Rows); //Hier wird die Query geöffnet und das Ergebnis in Arrays of string gespeichert if Assigned(AStream) then begin qry_Settings.SaveToStream(AStream, sfJSON); end; Finally Logic.Free; End; end; Nachstehend mal der gesamte Weg: ClientAPP:
Delphi-Quellcode:
Actually_MLifeData
procedure TFrm_Main_BSC.AdvGlowButton2Click(Sender: TObject); //Hier werden alle Maschine Staties abgefragt
begin Actually_MLifeData(Pnl_Top_CL1, true); //Wenn nur eine abgefragt wird, gibt es keine Probleme Actually_MLifeData(Pnl_Top_CL2, true); Actually_MLifeData(Pnl_Top_CL3, true); Actually_MLifeData(Pnl_Top_CL4, true); Actually_MLifeData(Pnl_Top_CL5, true); Actually_MLifeData(Pnl_Bottom_CL1, true); Actually_MLifeData(Pnl_Bottom_CL2, true); Actually_MLifeData(Pnl_Bottom_CL3, true); Actually_MLifeData(Pnl_Bottom_CL4, true); Actually_MLifeData(Pnl_Bottom_CL5, true); end;
Delphi-Quellcode:
Thread-Definition
procedure TFrm_Main_BSC.Actually_MLifeData(BPanel: TPanel; RefreshData: boolean = false);
var CB:TDBAdvMultiColumnDropDown; MThread: TMyThreads; begin MThread:= TMyThreads.Create; TThread.NameThreadForDebugging(BPanel.Name); Try CB := TDBAdvMultiColumnDropDown(FindComponent('MCDrbBx_'+BPanel.Name)); if CB.Items.Items[CB.ItemIndex].Text[1] = '' then Exit; MThread.TH_Actually_FactoryScreen_Start(True); MThread.TH_Actually_FactoryScreen.Panel := BPanel; MThread.TH_Actually_FactoryScreen.Refresh := RefreshData; MThread.TH_Actually_FactoryScreen.MaschinenId := CB.Items.Items[CB.ItemIndex].Text[1]; MThread.TH_Actually_FactoryScreen.Write_FactoryPanel := Write_MLifeData2; MThread.TH_Actually_FactoryScreen.Resume; Finally BPanel.Refresh; MThread.Free; End; end;
Delphi-Quellcode:
Anfrage der Daten von der ServerAPP
{ TMyThread_Actually_FactoryScreen }
procedure TMyThread_Actually_FactoryScreen.Execute; var MxSQL: TMxSQL; Logic: TLogic; begin inherited; MxSQL:= TMxSQL.Create; Logic:= TLogic.Create; Try MxSQL.Get_MLifeData(fCols_MLifeData, fRows_MLifeData, fMaschinenId, fPanel.Name, fRefresh); //Hier werden die Daten von der ServerApp abgefragt Synchronize(procedure begin if Assigned(fWrite_FactoryPanel) then begin fWrite_FactoryPanel(fPanel, fMaschinenId, fCols_MLifeData, fRows_MLifeData); end; end); Finally MxSQL.Free; Logic.Free; End; end;
Delphi-Quellcode:
Client Write Procedure ist in der Form definiert und schreibt die Daten in die entsprechende Felder.
function TMxSQL.Get_MLifeData(var Cols: TCols; var Rows: TRows; Maschinen_ID, BPanel: string; RefreshData: boolean = false): TStream;
var LClient: TxDataClient; LService: IMyReportsService; LSTream: TMemoryStream; RStream: TMemoryStream; MTable: TFDMemTable; Logic: TLogic; StreamString: WideString; Methode: string; Cols_Set_Main: TCols; Rows_Set_Main: TRows; begin MTable:= TFDMemTable.Create(nil); LClient := TXDataClient.Create; LStream := TMemoryStream.Create; RStream := TMemorySTream.Create; Logic := TLogic.Create; Try GetSettings('hlp_properties', Cols_Set_Main, Rows_Set_Main); ReadSettings('LifeMonitor_Methode',Cols_Set_Main, Rows_Set_Main); LClient.Uri:= DB_Unit.xData_Connect.URL; LService:= LClient.Service<IMyReportsService>; Methode := ReadSettings('LifeMonitor_Methode', Cols_Set_Main, Rows_Set_Main); if Methode = 'JSON' then begin // Weg über die vorhandene API-hier nicht relevant end else if Methode = 'QUERY' then begin LStream:=LService.Get_LifeMData(Maschinen_ID, RefreshData) as TMemoryStream; LStream.Position:= 0; MTable.Close; MTable.FieldDefs.Clear; MTable.Fields.clear; MTable.LoadFromStream(LStream, sfJSON); Logic.MTable_ColsRows(MTable, Cols, Rows); end else begin ShowMessage('Methode unbekannt'); Exit; end; Result:= RStream; Finally Logic.Free; LClient.Free; LStream.Free; MTable.Free; end; end; Server AP
Delphi-Quellcode:
Abfrage der Daten
function TMyReportsService.Get_LifeMData(Maschinen_ID: string; RefreshData: boolean = false; SendData: boolean = true): TStream;
var MxSQL: TMxSQL; LStream: TMemoryStream; begin MxSQL := TMxSQL.Create; LStream := TMemoryStream.Create; Try MxSQL.Get_act_MachineData(Maschinen_ID, LStream, RefreshData, SendData); Result := LStream; Finally MxSQL.Free; End; end;
Delphi-Quellcode:
Der Fehler scheint beim Öffnen der Query in dieser Funktion zu entstehen:
procedure TMxSQL.Get_act_MachineData(Machine_ID: string; LStream: TMemoryStream; RefreshData, SendData: boolean);
var Logic: TLogic; MsQuery: TFDQuery; CDMQuery: TFDQuery; DataTable: TFDMemTable; Operationslist: string; Cols_MData, Cols_OData: TCols; Rows_MData, Rows_OData: TRows; Cols_Set: TCols; Rows_Set: TRows; begin if (RefreshData = false) and (DB_Modul.Tmr_LM_RefreshData.Enabled = false) and (SendData = true) then begin RefreshData := true; DB_Modul.Tmr_LM_RefreshData.Enabled := true; DB_Modul.FLM_OfflineState := 0; DB_Modul.Write_LM_Protokoll('RefreshTimer aktiviert.'); end; if (RefreshData = false) and (DB_Modul.Tmr_LM_RefreshData.Enabled = true) and (SendData = true) then begin DB_Modul.Tmr_LM_RefreshData.Enabled := true; DB_Modul.FLM_OfflineState := 0; DB_Modul.Write_LM_Protokoll('OfflineState-Counter zurückgesetzt.'); end; if (DB_Modul.Tmr_LM_RefreshData.Enabled = false) then begin DB_Modul.Tmr_LM_RefreshData.Enabled := true; DB_Modul.FLM_OfflineState := 0; DB_Modul.Write_LM_Protokoll('RefreshTimer aktiviert. OfflineState-Counter zurückgesetzt.'); end; Logic := TLogic.create; try Logic.Set_Query_FDMngr(MsQuery, 'BDHAPS'); Logic.Set_Query_FDMngr(CDMQuery, 'BDHCDM'); if (RefreshData = true) then begin DB_Modul.Write_LM_Protokoll('RefreshProzess (' + Machine_ID + ') aktiviert.'); Get_Settings(Cols_Set, Rows_Set, 'hlp_properties'); //In dieser Funktion scheint der Fehler zu entstehen DB_Modul.Write_LM_Protokoll('GetSettings (' + Machine_ID + ')'); OperationsList := Read_Settings('Life_Data_OperationList', Cols_Set, Rows_Set); DB_Modul.Write_LM_Protokoll('ReadSettings (' + Machine_ID + ')'); MsQuery.SQL.Add('SELECT TOP 1 '+ 'CASE WHEN ( '+ 'concat(rj.JOB_ID, ' + QuotedStr(' ') + ', JOB_NAME) is NULL) then '+ 'LAG(Concat(rj.JOB_ID, ' + QuotedStr(' ') + ', rj.JOB_NAME)) OVER (ORDER BY re.TIME_LOCAL DESC) ELSE '+ 'concat(rj.JOB_ID, ' + QuotedStr(' ') + ',rj.JOB_NAME) end AS JOB_NAME, '+ 'CONVERT( varchar( 10 ), re.Time_Local, 104 ) AS Datum, '+ 'convert(char(5), re.Time_Local, 108) AS Startzeit, '+ 'rd.device_id, '+ 'rd.device_name, '+ 'ro.OPERATION_NAME, '+ 'ry.FAMILY_NAME AS Operator, '+ 'CASE WHEN ( '+ 'DateDiff(minute, re.Time_Local, LAG(re.Time_Local) OVER (ORDER BY re.TIME_LOCAL DESC)) is NULL) then '+ 'Concat(DateDiff(minute, re.Time_Local, GETDATE()),' + QuotedStr(':00 (') + ',convert(char(5), re.Time_Local, 108), ' + QuotedStr(')') + ') ELSE '+ 'ConCat(DateDiff(minute, re.Time_Local, LAG(re.Time_Local) OVER (ORDER BY re.TIME_LOCAL DESC)),' + QuotedStr(':00 (') + ',convert(char(5), re.Time_Local, 108), ' + QuotedStr(')') + ') end AS Dauer, '+ '(SELECT SPEED FROM RBC_DEVICE_BASIC_INTERVALS RDB WHERE RDB.DEVICE_BASIC_INTERVAL_KEY = '+ '(SELECT MAX(Device_Basic_interval_key) FROM RBC_DEVICE_BASIC_INTERVALS RDBI WHERE RDBI.DEVICE_KEY = re.DEVICE_KEY)) AS Speed, '+ 'CASE WHEN (max(ws.GOOD_CYCLES) = 0 OR max(ws.planned_good_cycles)=0) THEN 0 ELSE '+ 'ROUND(max(ws.GOOD_CYCLES)/max(ws.planned_good_cycles),2)*100 END as PERCENT_COMPLETED, '+ 'ws.PLANNED_GOOD_AMOUNT, '+ 'ws.GOOD_CYCLES, '+ 'ro.OPERATION_KEY, '+ 'ws.WORK_STEP_NAME, '+ 'sj.CUSTOMER_NAME '+ 'from RPS_EVENTS re '+ 'LEFT JOIN RPS_WORK_STEPS ws ON ws.WORK_STEP_KEY = re.WORK_STEP_KEY '+ 'LEFT JOIN RPS_JOBS rj ON rj.JOB_KEY = ws.JOB_KEY '+ 'LEFT JOIN RPS_EMPLOYEE_ACTIVITIES ea ON ea.WORK_STEP_KEY = ws.WORK_STEP_KEY '+ 'LEFT JOIN RPS_OPERATIONS ro ON ro.OPERATION_KEY = re.OPERATION_KEY '+ 'LEFT JOIN RBC_DEVICES rd ON rd.device_key = re.device_key '+ 'LEFT JOIN RBC_EMPLOYEES ry ON ry.EMPLOYEE_KEY = ea.EMPLOYEE_KEY '+ 'LEFT JOIN SPS_JOB sj ON sj.JOBID = rj.JOB_ID '+ 'WHERE rd.DEVICE_ID = :Device_ID '+ 'AND CONVERT( date, re.TIME_LOCAL) = CONVERT( date, GETDATE()) '+ 'AND ( re.OPERATION_KEY IN ( ' + OperationsList + ' ) OR (re.OPERATION_KEY IS NULL) AND (rd.DEVICE_ID = ' + QuotedStr('Suprasetter@BDHSHOOTER') + ')) '+ 'GROUP BY rj.JOB_NAME, '+ 'rj.job_id, '+ 're.Time_Local, '+ 're.Device_key, '+ 'rd.device_id, '+ 'rd.device_name, '+ 're.OPERATION_KEY, '+ 'ro.OPERATION_KEY, '+ 'ro.OPERATION_NAME, '+ 'ry.FIRST_NAME, '+ 'ry.FAMILY_NAME, '+ 'ws.PERCENT_COMPLETED, '+ 'ws.PLANNED_GOOD_AMOUNT, '+ 'ws.GOOD_CYCLES, '+ 'ws.WORK_STEP_NAME, '+ 'sj.CUSTOMER_NAME '+ 'ORDER BY re.TIME_LOCAL desc '); MsQuery.ParamByName('Device_ID').AsString := Machine_ID; MsQuery.Open; //Daten in Datentabelle kopieren und für Clientabfragen verfügbar machen DataTable := TFDMemtable(DB_Modul.FindComponent('FDTbl_LM_' + StringReplace(Machine_ID, '@','_',[rfIgnoreCase, rfReplaceAll]))); DataTable.CopyDataSet(MsQuery, [coStructure, coRestart, coAppend]); DB_Modul.Write_LM_Protokoll('Refresh DatenTabelle Maschine ' + Machine_ID); DB_Modul.Write_LM_Protokoll('RefreshProzess (' + Machine_ID + ') beendet.') end; if SendData then begin if Assigned(LStream) then begin DataTable := TFDMemtable(DB_Modul.FindComponent('FDTbl_LM_' + StringReplace(Machine_ID, '@','_',[rfIgnoreCase, rfReplaceAll]))); if DataTable.State = dsInactive then begin DB_Modul.Write_LM_Protokoll('FDTable('+Machine_ID+') ist inaktiv. Wartezeit 2 Sek.'); Sleep(2000); end; if DataTable.State = dsBrowse then begin DataTable.SaveToStream(LStream, sfJSON); DB_Modul.Write_LM_Protokoll('Sent Daten ('+Machine_ID+') LifeMonitor.'); end else begin DB_Modul.Write_LM_Protokoll('ERROR: FDTable nicht aktiv ('+Machine_ID+').'); end; end else begin DB_Modul.Write_LM_Protokoll('ERROR: No Stream assigned ('+Machine_ID+').'); end; end; Finally CDMQuery.Free; MsQuery.Free; Logic.Free; end; end;;
Delphi-Quellcode:
ExecQuery
procedure TMxSQL.Get_Settings(var Cols: TCols; var Rows: TRows; Tabelle: string; AStream: TStream);
var Logic: TLogic; qry_Settings: TFDQuery; begin Logic:= TLogic.Create; Logic.Set_Query_FDMngr(qry_Settings, 'BDHMySQL'); Try qry_Settings.sql.Add('select * from ' + Tabelle); ExecQuery(qry_Settings, Cols, Rows); //Hier wird die Query geöffnet und das Ergebnis in Arrays of string gespeichert if Assigned(AStream) then begin qry_Settings.SaveToStream(AStream, sfJSON); end; Finally Logic.Free; End; end;
Delphi-Quellcode:
function TMxSQL.ExecQuery (query: TFDQuery; var Cols: TCols; var Rows: TRows; AddRows: integer = 0): integer;
var I, J: integer; begin Query.Open; //Hier kracht es SetLength(Cols,0); SetLength(Rows,0, 0); SetLength(Cols,Query.FieldCount); SetLength(Rows,Query.FieldCount, Query.RecordCount+AddRows); //+1 für FloatFooter for J:=0 to Query.FieldCount -1 do begin Cols[J]:=Query.Fields.Fields[J].FieldName; end; for I:=0 to Query.RecordCount -1 do begin for J:=0 to Query.FieldCount -1 do begin Rows[J,I]:=Query.Fields.Fields[J].AsString; end; query.Next; end; Result := Query.RecordCount; end; Aber ich vermute, dass es sich um ein generelles Problem handelt und nichts wirklich mit dieser Funktion zu tun hat. Ich wäre für Eure Unterstützung wirklich sehr dankbar. Habe keine Idee mehr, wie ich mich dem Problem noch nähern soll. Vielen Dank Patrick
Patrick
Geändert von Ykcim (13. Dez 2023 um 17:04 Uhr) |
Zitat |
Registriert seit: 29. Dez 2006 Ort: NRW 831 Beiträge Delphi 10.4 Sydney |
#2
Hallo Zusammen,
das ist kein PUSH! Ich habe von dem TFDManager die Einstellung ResourceOptions/MaxCursors von -1 auf 50 geändert. Ich dachte -1 stände für unbegrenzt... Dann ist die Anwendung 15 Minuten mit einer Abfrage alle 20 Sekunden ohne Fehler durchgelaufen. Ich habe zwar im TaskManager beobachtet, dass ich einen Speicher irgendwo nicht freigegeben habe, denn der Speicher ist bei jeder Abfrage von der ServerApp um ca. 3MB gestiegen. Nach 15 Minuten kam dann leider doch wieder eine Fehler bei der gleichen Funktion mit dem Hinweis "Vorherige Aktion noch nicht abgeschlossen". Das ist schon zumindest ein Schritt in die richtige Richtung, den ich Euch mitteilen wollte. LG Patrick
Patrick
|
Zitat |
Registriert seit: 27. Nov 2017 2.508 Beiträge Delphi 7 Professional |
#3
Du öffnest im Ablauf mehrere Querys. Im Quelltext kann ich aber keine entsprechende Anzahl von Close finden.
Vor der Freigabe der Querys würd' ich explizit ein Close aufrufen.
Delphi-Quellcode:
Wenn AStream zwingend erforderlich ist, kann man sich bei if not Assigned(AStream)
die Routine sparen, daher würd' ich die Prüfung an den Anfang verschieben.
procedure TMxSQL.Get_Settings(var Cols: TCols; var Rows: TRows; Tabelle: string; AStream: TStream);
var Logic: TLogic; qry_Settings: TFDQuery; begin Logic:= TLogic.Create; Logic.Set_Query_FDMngr(qry_Settings, 'BDHMySQL'); Try qry_Settings.sql.Add('select * from ' + Tabelle); ExecQuery(qry_Settings, Cols, Rows); //Hier wird die Query geöffnet und das Ergebnis in Arrays of string gespeichert if Assigned(AStream) then begin qry_Settings.SaveToStream(AStream, sfJSON); end; qry_Settings.Close; // Wird nicht mehr benötigt, daher zumachen. Finally qry_Settings.Free; // Freigabe der Query, oder erfolgt die Freigabe // automatisch in Logic.Free? Logic.Free; End; end;
Delphi-Quellcode:
Und ehrlich gesagt hab' ich keine Ahnung, ob es das geschielderte Ursprungsproblem löst, aber in Bezug auf
procedure TMxSQL.Get_Settings(var Cols: TCols; var Rows: TRows; Tabelle: string; AStream: TStream);
var Logic: TLogic; qry_Settings: TFDQuery; begin if not Assigned(AStream) then exit; // und passende Fehlermeldung ins Protokoll, sonst sucht man sich ggfls. 'nen Wolf nach 'nem vollkommen anderen Fehler im SQL, der zwar nicht existiert, aber durch den fehlenden Stream "übertünscht" wird. Logic:= TLogic.Create; Logic.Set_Query_FDMngr(qry_Settings, 'BDHMySQL'); Try qry_Settings.sql.Add('select * from ' + Tabelle); ExecQuery(qry_Settings, Cols, Rows); // Hier wird die Query geöffnet und das Ergebnis in Arrays of string gespeichert qry_Settings.SaveToStream(AStream, sfJSON); qry_Settings.Close; // Wird nicht mehr benötigt, daher zumachen. Finally qry_Settings.Free; // Freigabe der Query, oder erfolgt die Freigabe // automatisch in Logic.Free? Logic.Free; End; end;
Zitat von Ykcim:
Ich habe zwar im TaskManager beobachtet, dass ich einen Speicher irgendwo nicht freigegeben habe, denn der Speicher ist bei jeder Abfrage von der ServerApp um ca. 3MB gestiegen.
|
Zitat |
Registriert seit: 29. Dez 2006 Ort: NRW 831 Beiträge Delphi 10.4 Sydney |
#4
Hallo Zusammen,
ich glaube, es ist gelöst. Ich habe den Querys, die erstellt wurden, immer eine Connection gegeben, die auf die ConnectionDefs des Managers verweisen und nachdem sie nicht mehr benötigt werden, wie freigegeben werden. Ich habe mich dabei an diesem Artikel orientiert: https://docwiki.embarcadero.com/RADS...ding_(FireDAC) Das sieht dann exemplarisch so aus:
Delphi-Quellcode:
procedure TMxSQL.Get_Settings(var Cols: TCols; var Rows: TRows; Tabelle: string; AStream: TStream);
var Logic: TLogic; qry_Settings: TFDQuery; oConn_MySQL: TFDConnection; begin Logic:= TLogic.Create; Logic.Set_Query_FDMngr2(qry_Settings, oConn_MySQL, 'BDHMySQL'); Try qry_Settings.sql.Add('select * from ' + Tabelle); ExecQuery(qry_Settings, Cols, Rows); if Assigned(AStream) then begin qry_Settings.SaveToStream(AStream, sfJSON); end; qry_Settings.Close; oConn_MySQL.Close; Finally qry_Settings.Free; oConn_MySQL.Free; Logic.Free; End; end;
Delphi-Quellcode:
Damit ist die Anwendung heute ca. 40 Minuten ohne Fehler gelaufen... Ich hoffe, dass war wirklich die Lösung
procedure TLogic.Set_Query_FDMngr2(var Query: TFDQuery; var oConn: TFDConnection; Database: string);
begin oConn := TFDConnection.Create(nil); oConn.ConnectionDefName := Database; Query:= TFDQuery.Create(nil); query.Connection:= oConn; Query.SQL.Clear; Query.FetchOptions.AutoFetchAll; Query.FetchOptions.Mode := fmAll; end; Ich habe mir die Verbindungen auf der Datenbank angesehen, dass waren ca. 11 die aber konstant blieben... @Delphi.Narium Du hattest Recht, ich hatte qry_Settings.Free; vergessen. Jetzt läuft der Speicher bei der App nicht mehr voll.
Zitat:
Wenn AStream zwingend erforderlich ist, kann man sich bei if not Assigned(AStream) die Routine sparen, daher würd' ich die Prüfung an den Anfang verschieben.
Vielen Dank, ich bastel mal weiter und hoffe, keine bösen Überraschungen zu erleben. LG Patrick
Patrick
|
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |