I have a global object in Delphi that reads some object definitions from disk and creates a cache of worker objects representing them.
There is also a method on the global object that returns an instance of a worker object - this method needs to be blocked if the global object is still inside the method that is reading the disk/creating the workers. This load/create method will be called on an intermittent basis (once per day) to force reloading of the xml files.
I've created a simplified version of the code so that I can get some feedback on the correct use of the TMonitor record. First the declarations
type
// a simplified version of the worker, I just get it to return the timestamp of when it was created - in real life this actually does something :)
TWorker = class
private
FValue: TDateTime;
public
property Value: TDatetime read FValue;
end;
// An interface modelling the global object
IStorage = interface
['{8FD599BE-4064-45DE-8FFC-96A9D2C812F1}']
function GetWorker: TWorker;
procedure ReLoad;
end;
// a global pointer to a function that has access to a singleton instance of the worker. Creating this
var
Storage: function: IStorage;
Now the implementation
// a concrete implementation of the interface
type
TStorage = class(TInterfacedObject, IStorage)
private
FIsLoaded: Boolean; // flag to indicate if objects have been loaded
FList: TObjectList<TWorker>; // list of Worker objects (takes ownership)
protected
{ IStorage }
function GetWorker: TWorker;
procedure Load;
public
constructor Create;
destructor Destroy; override;
end;
// the method to "load/reload" from disk - I've just simplified this-in real life it would lock then clear the cache and recreate the objects
procedure TStorage.ReLoad;
begin
TMonitor.Enter(Self);
try
FIsLoaded := False;
for var I := 0 to 15 do
Sleep(250);
FList.Clear;
FIsLoaded := true;
TMonitor.PulseAll(Self);
finally
TMonitor.Exit(Self);
end;
end;
// and the method that serves out instances of worker objects
// this is the one that is causing me to double check my understanding of TMonitor
function TStorage.GetWorker: TWorker;
begin
if not FIsLoaded then
begin
TMonitor.Enter(Self);
try
while not FIsLoaded do
TMonitor.Wait(Self, INFINITE);
finally
TMonitor.Exit(Self);
end;
end;
Result := TWorker.Create;
Result.FValue := Now;
end;
I'm using the TMonitor instance (locking the global object) to prevent a calling thread from proceeding with GetWorker until the boolean FIsLoaded has been set to true.
Am I on the right track ? In particular my use of the Wait and PulseAll methods.
I'm using Delphi 11.3 if that makes any difference, as google searches indicate some issues using TMonitor but around 10 years ago.
You're on the right track here. I would recommend using a private instance field for the lock rather than Self. This would stay away from the "locking this (Self) is bad" issue. Otherwise some external "bad actor" could take the same lock you're using internally.
If you have the System.pas source, there should be a rather large comment describing TMonitor, including a link to an article describing its inspiration.