After almost one year of trying different options I haven't still found a solution for using FPC generics as described (except, of course, the obvious solution to copy&paste the code snippet about 10 times for all possible variable types that I need, which is not really nice and in principle not good for code maintenance).
The idea of this code is simply to use something very similar of global variables, but with safety against unsynchronized access of many different threads.
Examples could be any global parameter in a program, that is accessed by multiple threads and forms, such as GUI colors. Performance is not critical, since those parameters may be accessed only seldomly (upon OnClock events, in particular).
TYPE TThreadSafeValue<T>
= CLASS
PROTECTED
Synchronizer:TMultiReadExclusiveWriteSynchronizer;
CriticalSectionChange:TCriticalSection;
FUNCTION ReadValue:T;
PROCEDURE WriteValue(NewValue:T);
FUNCTION ReadDefaultValue:T;
PROCEDURE WriteDefaultValue(NewDefault:T);
FUNCTION GetChanged:Boolean;
PRIVATE
LocalValue:T;
LocalDefault:T;
LocalChanged:Boolean;
PUBLIC
CONSTRUCTOR Create;
DESTRUCTOR Destroy; OVERRIDE;
PROCEDURE SetDefault;
PUBLISHED
PROPERTY Value :T READ ReadValue WRITE WriteValue;
PROPERTY DefaultValue:T READ ReadDefaultValue WRITE WriteDefaultValue;
PROPERTY Changed:Boolean READ GetChanged;
END;
CONSTRUCTOR TThreadSafeValue<T>.Create;
BEGIN
INHERITED;
Synchronizer:=TMultiReadExclusiveWriteSynchronizer.Create;
CriticalSectionChange:=TCriticalSection.Create;
// initialize MyValue with default value of type
LocalValue:=Default(T);
END;
DESTRUCTOR TThreadSafeValue<T>.Destroy;
BEGIN
Synchronizer.Destroy;
CriticalSectionChange.Destroy;
INHERITED;
END;
FUNCTION TThreadSafeValue<T>.ReadValue;
VAR Dummy:T;
BEGIN
// Read MyValue safely into Dummy
Synchronizer.BeginRead;
Dummy:=LocalValue;
Synchronizer.EndRead;
// and return as result
Result:=Dummy;
END;
PROCEDURE TThreadSafeValue<T>.WriteValue(NewValue:T);
VAR OldValue:T;
BEGIN
Synchronizer.BeginWrite;
OldValue:=LocalValue;
LocalValue:=NewValue;
Synchronizer.EndWrite;
CriticalSectionChange.Enter;
// operator <> does not work with generics!
// LocalChanged:=LocalChanged OR (OldValue<>NewValue);
LocalChanged:=True;
CriticalSectionChange.Leave;
END;
FUNCTION TThreadSafeValue<T>.ReadDefaultValue;
VAR Dummy:T;
BEGIN
// Read MyValue safely into dummy
Synchronizer.BeginRead;
Dummy:=LocalDefault;
Synchronizer.EndRead;
// and return as result
Result:=Dummy;
END;
PROCEDURE TThreadSafeValue<T>.WriteDefaultValue(NewDefault:T);
VAR OldValue:T;
BEGIN
Synchronizer.BeginWrite;
LocalDefault:=NewDefault;
OldValue:=LocalValue;
LocalValue:=NewDefault;
Synchronizer.EndWrite;
CriticalSectionChange.Enter;
// operator <> does not work with generics!
// LocalChanged:=LocalChanged OR (OldValue<>NewDefault);
LocalChanged:=True;
CriticalSectionChange.Leave;
END;
PROCEDURE TThreadSafeValue<T>.SetDefault;
VAR MyDefaultValue:T;
BEGIN
Synchronizer.BeginRead;
MyDefaultValue:=LocalDefault;
Synchronizer.EndRead;
Synchronizer.BeginWrite;
LocalValue:=MyDefaultValue;
Synchronizer.EndWrite;
END;
FUNCTION TThreadSafeValue<T>.GetChanged:Boolean;
VAR MyChanged:Boolean;
BEGIN
CriticalSectionChange.Enter;
MyChanged:=LocalChanged;
LocalChanged:=False;
CriticalSectionChange.Leave;
Result:=MyChanged;
END;
I assume that this is a very common problem and - for my understanding - a threadvar does not help at all here.
How are others dealing with this?