Ahoi
DP,
ich lasse mir in einem Thread Daten zur Anzeige in einem Graphen aus einer
MySQL DB holen. Dies mache ich immer, wenn jemand im Graphen die Achsen verschiebt oder den X-Zoom ändert. Das klappt bisher auch recht gut. Leider passiert es aber insbesondere beim Zoom mit dem Scrollrad, dass eine neue Abfrage gestartet wird bevor die davor fertig ist. Durch das entsprechend nötige Syncen führt das am Ende dazu, dass man nur maximal so schnell scrollen kann, wie die
DB die Daten holt.
Oder genauer: Man sieht das Endergebnis erst dann, wenn auch der letzte Thread fertig geworden ist - welcher leider noch nichtmals immer der ist, der zuletzt gestartet wurde, wodurch man manchmal unvollständige Graphen bekommt, da sie noch zu einem anderen Zeitabschnitt oder Zoom gehören.
Aktuell behelfe ich mir, in dem ich ggf. noch laufende Threads bei Eingang einer neuen Zoom- oder Verschiebeoperation versuche sich "gracefully" beenden zu lassen, und falls das nicht innerhalb von 100ms passiert ist, den Thread mit KillThread() brutal wegzuschießen. Das geht! Aber leider befürchte ich dadurch massive Speicherlöcher und irgendwann auch
Handle-Knappheit, da sich natürlich jeder Thread eine eigene UniConnection und UniQuery erstellt, die beim Abschießen wahrscheinlich nicht freigegeben werden, und ich keine Ahnung habe, was auf Seiten des
SQL Servers noch offen bleibt.
Gibt es irgend eine Möglichkeit eine gerade laufende
Query vor Rückkehr abzubrechen? Das wäre die mir liebste und einfachste Lösung.
Delphi-Quellcode:
procedure TKATChartUpdateThread.Execute;
var
con: TUniConnection;
qry: TUniQuery;
startDate, endDate: TDateTime;
tmpSeries: TObjectList;
seriesIDs: TList;
aborted: Boolean;
begin
aborted := false;
seriesIDs := nil;
Screen.Cursor := crAppStart;
con := TUniConnection.Create(nil);
con.Server := FChart.FConnection.Server;
con.Database := FChart.FConnection.Database;
con.Username := FChart.FConnection.Username;
con.Password := FChart.FConnection.Password;
con.ProviderName := FChart.FConnection.ProviderName;
con.Connect;
qry := TUniQuery.Create(nil);
qry.Connection := con;
if Abort then begin aborted := true; Exit; end;
try
startDate := UnixToDateTime(FChart.FLeftXValue);
endDate := UnixToDateTime(FChart.FRightXValue);
if Abort then begin aborted := true; Exit; end;
tmpSeries := TObjectList.Create(true);
FChart.FSeriesIDLock.Enter;
try
seriesIDs := TList.Create;
seriesIDs.Assign(FChart.FSeriesIDs);
finally
FChart.FSeriesIDLock.Leave;
end;
if Abort then begin aborted := true; tmpSeries.Free; Exit; end;
for i := 0 to seriesIDs.Count-1 do
begin
k := tmpSeries.Add(TKATChartSeries.Create(Integer(seriesIDs[i])));
qry.SQL.Text := BuildSumSQL(Integer(seriesIDs[i]), con);
qry.ParamByName('t1').AsDateTime := startDate;
qry.ParamByName('t2').AsDateTime := endDate;
qry.Open;
while not qry.Eof do
begin
TKATChartSeries(tmpSeries[k]).AddValue(qry.FieldByName('qMIN').AsFloat,
qry.FieldByName('qMAX').AsFloat,
qry.FieldByName('qAVG').AsFloat,
DateTimeToUnix(qry.FieldByName('qDate').AsDateTime));
qry.Next;
if Abort then begin aborted := true; tmpSeries.Free; Exit; end;
end;
qry.Close;
end;
if Abort then begin FChart.IsBusy := false; aborted := true; tmpSeries.Free; Exit; end;
FChart.FSeriesLock.Enter;
try
if Assigned(FChart.FSeries) then
FreeAndNil(FChart.FSeries);
FChart.FSeries := tmpSeries;
finally
FChart.FSeriesLock.Leave;
end;
finally
Screen.Cursor := crDefault;
qry.Free;
con.Free;
if Assinged(seriesIDs) then
seriesIDs.Free;
if not aborted then
Synchronize(FChart.UpdateReady);
end;
end;
Die ganzen "if Aborted then ..." sind mein Versuch den Thread vernünftig zu beenden. Die Aufrufende Methode sieht so aus:
Delphi-Quellcode:
procedure TKATChart.MakeSeries;
begin
if Assigned(FUpdateThread) then
begin
FUpdateThread.Abort := true;
if WaitForSingleObject(FUpdateThread.Handle, 100) = WAIT_TIMEOUT then
TerminateThread(FUpdateThread.Handle, 0);
end;
FUpdateThread := TKATChartUpdateThread.Create(self);
end;
Die einzige Operation die in dem Thread potenziell lange dauert ist die Zeile
qry.Open;
. Aber da komme ich auf keine mir bekannte Art mehr zwischen. Was kann man hier tun? Insbesondere fürchte ich, dass nach dem TerminateThread die
DB noch immer denkt das Ergebnis wird gebraucht, und die Operation unnötig zu Ende führt, was mich Performance bei allen nachfolgenden Queries kosten dürfte. Und Ressourcen die dauerhaft offen bleiben. Sehr unschön.
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)