Moin!
Der Titel ist etwas allgemein, aber mein Problem ist etwas umfassender in der Beschreibung und schwer in 3-4 Worte zu fassen. Folgende Basis: Ich habe eine Formularklasse von TCustomForm abgeleitet. Diese besitzt einen Thread, der von aussen mit Aktualisierungsaufträgen gefüttert wird, wodurch Komponenten auf dem Formular mit Daten aus einer Datenbank befüllt werden. Hintergrund ist ein Client-Server-System zur Anlagenvisualisierung in der Industrie, bei der ein Server Daten aus einer SPS pollt, diese in die
DB schreibt, und angemeldete Clients via
TCP über Neuigkeiten benachrichtigt. Die genannten Formulare reagieren auf eben diese Benachrichtigungen.
(Diese enthalten je einen Tabellennamen und eine Liste der Satz-IDs in denen Änderungen waren.)
Die Struktur in (Teil-)(Pseudo-)Codefragmenten:
Delphi-Quellcode:
unit MyForm;
const
WM_VCLUPDATE = WM_USER + 943;
type
TBytes =
packed array of Byte;
TCommBuffer =
class
public
buf: TBytes;
end;
TVCLUpdateData =
class
public
Instance: TObject;
ClassType: TClass;
PropName:
String;
Value: Variant;
end;
TUpdateThread =
class(TThread)
private
procedure MakeUpdatesTabelle1;
procedure MakeUpdatesTabelle2;
protected
procedure Execute;
override;
public
RawBuffers: TObjectList;
end;
TMyForm =
class(TCustomForm)
private
UpdateThread: TUpdateThread;
procedure HandleUpdates(
var Msg: TMessage);
message WM_VCLUPDATE;
protected
procedure DoShow;
override;
public
RelevanteKomponenten: TObjectList;
UpdateItems: TObjectList;
UpdateCS: TCriticalSection;
AddCS: TCriticalSection;
constructor Create;
// Füllt "RelevanteKomponenten" anhand von Datenbankeinträgen mit den Instanzen auf dem Formular
procedure AddBuffer(buf: TBytes);
end;
implementation
// Wird vom Hauptformular im Ereignishandler OnSocketRead() aufgerufen (ein TClientSocket)
procedure TMyForm.AddBuffer(buf: TBytes);
begin
if self.Visible
then
begin
AddCS.Enter;
try
UpdateThread.RawBuffers.Add(TCommBuffer.Create(copy(buf, 0, Length(buf)));
finally
AddCS.Leave;
end;
end;
end;
procedure TMyForm.HandleUpdates(
var Msg: TMessage);
var
item: TVCLUpdateItem;
begin
UpdateCS.Enter;
try
while UpdateItems.Count>0
do
begin
item := TCVUpdateItem(UpdateItems[0]);
try
if Assigned(item.Instance)
and (item.Instance
is item.ClassType)
and ((item.Instance
as TControl).Parent.Visible)
then
SetPropValue((item.Instance
as item.ClassType), item.PropName, item.Value);
finally
UpdateItems.Delete(0);
end;
end;
finally
UpdateCS.Leave;
end;
end;
procedure TMyForm.DoShow;
begin
inherited DoShow;
SetzeAlleUpdateFlagsImThread;
// Damit nach Sichtbarmachen ein komplettes Update aus der DB geschlabbert wird
end;
procedure TUpdateThread.Execute;
begin
repeat
if RawBuffers.Count > 0
then
InterpretiereBufferInhalteUndSetzeFlagsFürAusstehendeUpdates;
// Umrahmt von "AddCS.Enter; -- AddCS.Leave;"
if UpdateFlagFürTabelle1
and Tabelle1Relevant
then
MakeUpdatesTabelle1;
if UpdateFlagFürTabelle2
and Tabelle2Relevant
then
MakeUpdatesTabelle2;
// usw.
Sleep(1);
until Terminated;
end;
procedure TUpdateThread.MakeUpdatesTabelle1;
var
obj: TObject;
begin
SelectDatenFromSQL;
FForm.UpdateCS.Enter;
try
while not Query.Eof
do
begin
obj := FForm.FindeKomponenteAusInDessenCreateErstellterListeRelevanterKomponenten('
NameAusTabelle');
FForm.UpdateItems.Add(TVCLUpdateData.Create(obj, obj.
Class, '
PropertyAusTabelle', WertAusTabelle));
Query.Next;
end;
finally
FForm.UpdateCS.Leave;
end;
PostMessage(FForm.Handle, WM_VCLUPDATE, 0, 0);
end;
Dass mir gelegentlich das Programm komplett stehen bleibt, scheint gerade gelöst zu sein: Der Server nutzt einen gemeinsamen TServerSocket, über den mehrere Threads Nachrichten verschicken (ein Thread pro SPS-Verbindung), und obwohl ich die Notify-Methoden gegeneinander verriegel (der Server läuft auch klaglos), scheint das beim Empfang auf Clientseite Probleme zu machen. Hat der Server nur einen Thread, scheint das zu gehen.
Ein anderes Problem bleibt aber! Ab und zu, ganz selten und schwer reproduzierbar hört ein beliebiges Formular auf Basis der o.g. TMyForm-Klasse auf sich upzudaten. Logging brachte jedoch zutagen, dass der UpdateThread weiterhin läuft, und auch der Messagehandler reagiert - es wird einfach nur nicht mehr Neugezeichnet, und die Controls reagieren auch nicht mehr auf Clicks und Tastatur.
Jetzt war ich schon SO vorsichtig was das Fummeln an
VCL Komponenten in Kombination mit Threads angeht, und dennoch hängt es gelegentlich. Allerdings auch gerne mal erst nach 2-5 Tagen, in denen das Programm durchlief und tausende Updatezyklen problemlos durchlaufen hat.
Ab und an - ebenfalls selten, aber sehr störend: Wenn ich zwischen zwei solcher Formulare hin und her schalte (das alte .Hide, das neue .Show) bleibt ab und an das gesamte Clientprogramm stehen. Noch bevor das Komplett-Update im DoShow überhaupt angestoßen ist.
Wo kann ich hier noch auf einen Hammer laufen? Ich debugge mir hiermit schon 2 Tage einen Wolf, und seh vermutlich nicht mehr alles so klar.
"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)