Das sind im Endeffekt ja viele verschiedene Fragen auf einmal.
Ich denke bei den wenigstens Anwendungen ist das wirklich interessant ob im Hintergrund eine TCriticalSection, ein TMonitor, ein TMultiReadSonstwas zum Einsatz kommt. Die paar Nanosekunden sparen wir uns an dieser Stelle am besten vorerst...
Ich finde das ziemlich kompliziert gelöst. Erstens finde ich es fehl am Platz dass der Thread sich darum kümmern muss hier etwas in den Hauptthread zu pushen. Der Thread kann ein Event anbieten, das Formular registriert sich dort. Wenn das Formular es braucht dass es im Hauptthread ausgeführt wird, macht es das bitte selbst.
Wie du schon bemerkt hast ist ein Riesenproblem dass der Event-Handler ausgeführt wird während der Zugriff noch gesperrt ist. Wenn dein Formular jetzt z.B. den Wert noch einmal lesen will hängt alles - Weil deine TMyThread-Instanz hat es noch gesperrt. Folglich:
- Zugriff sperren
- Wert setzen, merken ob vorher anders war
- Sperre aufheben
- Ist der Wert vorher anders gewesen? Change-Event auslösen
Zweitens würde ich mir verschiedene Events sparen - Vielleicht ist bei mir die Welt viel einfacher, aber in 90 % der Fälle will man wirklich nur darauf reagieren
dass sich etwas geändert hat und pinselt dann die Oberfläche neu. Sicher könnte man etwas
Compiler-Strom sparen das alles haarklein aufzuteilen, aber im ersten Wurf braucht man das sicher nicht.
Eine kleine Änderung die ich mir noch erlaubt habe ist dass ich dem Thread nicht zwei oder mehr Werte direkt geben würde - Lager die Werte nochmal in eine einfache Struktur aus, einen Record (also Wertetyp).
Das sähe am Schluss dann so aus:
Delphi-Quellcode:
unit Unit2;
interface uses
System.Classes,
System.SyncObjs;
type
TData =
record
Prop1: Real;
Prop2: Real;
end;
TDataChangeEventHandler =
procedure(
const data: TData)
of object;
TMyThread =
class(TThread)
private var
handler: TDataChangeEventHandler;
FCount: Integer;
private
procedure incCounter();
inline;
protected var
data: TData;
mutex: TCriticalSection;
protected
procedure Execute();
override;
procedure setProp1(
const value: Real);
procedure setProp2(
const value: Real);
procedure invokeChangeEvent();
inline;
public
constructor Create(CreateSuspended: Boolean);
destructor Destroy();
override;
procedure setOnChange(
const handler: TDataChangeEventHandler);
function getOnChange(): TDataChangeEventHandler;
function getData(): TData;
end;
implementation uses System.SysUtils;
{ TMyThread }
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
mutex := TCriticalSection.Create();
end;
destructor TMyThread.Destroy();
begin
mutex.Free();
inherited;
end;
procedure TMyThread.Execute();
begin
while (
not Terminated)
do
begin
if Odd(FCount)
then
setProp1(FCount)
else
setProp2(FCount);
incCounter();
TThread.Sleep(50);
end;
end;
function TMyThread.getData(): TData;
begin
mutex.Acquire();
try
Result := data;
finally
mutex.Release();
end;
end;
function TMyThread.getOnChange(): TDataChangeEventHandler;
begin
mutex.Acquire();
try
Result := handler;
finally
mutex.Release();
end;
end;
procedure TMyThread.incCounter();
begin
if (FCount < Integer.MaxValue)
then
Inc(FCount)
else
FCount := Integer.MinValue;
end;
procedure TMyThread.invokeChangeEvent();
var
_handler: TDataChangeEventHandler;
begin
_handler := getOnChange();
if Assigned(_handler)
then
_handler(data);
end;
procedure TMyThread.setOnChange(
const handler: TDataChangeEventHandler);
begin
mutex.Acquire();
try
self.handler := handler;
finally
mutex.Release();
end;
end;
procedure TMyThread.setProp1(
const value: Real);
var
hasChanged: Boolean;
begin
mutex.Acquire();
try
hasChanged := (data.Prop1 <> value);
if hasChanged
then
data.Prop1 := value;
finally
mutex.Release();
end;
if hasChanged
then
invokeChangeEvent();
end;
procedure TMyThread.setProp2(
const value: Real);
var
hasChanged: Boolean;
begin
mutex.Acquire();
try
hasChanged := (data.Prop2 <> value);
if hasChanged
then
data.Prop2 := value;
finally
mutex.Release();
end;
if hasChanged
then
invokeChangeEvent();
end;
end.