Files
MycLib/Src/Myc.Futures.pas
T
Michael Schimmel 86e2729a52 refactoring tasks
2025-06-02 01:14:33 +02:00

110 lines
3.4 KiB
ObjectPascal

unit Myc.Futures;
interface
uses
System.SysUtils,
Myc.Signals, Myc.Core.Tasks;
type
// Represents the eventual result of an asynchronous operation.
IMycFuture<T> = interface
{$REGION 'property access'}
function GetResult: T;
function GetDone: IMycState;
{$ENDREGION}
// Provides access to the computed result.
property Result: T read GetResult;
// Provides access to the completion state.
property Done: IMycState read GetDone;
end;
// Abstract base class for IMycFuture<T> implementations.
TMycFuture<T> = class abstract(TInterfacedObject, IMycFuture<T>)
protected
function GetResult: T; virtual; abstract;
function GetDone: IMycState; virtual; abstract;
end;
// Concrete future implementation triggered by an initial state, executing a TFunc<T>.
TMycInitStateFuncFuture<T> = class(TMycFuture<T>)
private
FInit: TMycSubscription;
FDone: IMycLatch;
FResult: T;
protected
function GetResult: T; override;
function GetDone: IMycState; override;
public
// Creates a future that executes AProc after AInitState is set, using ATaskFactory.
constructor Create(const ATaskFactory: IMycTaskFactory; const AInitState:
IMycState; AProc: TFunc<T>);
// Cleans up resources, primarily by unsubscribing from the initial state.
destructor Destroy; override;
end;
implementation
{ TMycInitStateFuncFuture<T> }
constructor TMycInitStateFuncFuture<T>.Create(const ATaskFactory:
IMycTaskFactory; const AInitState: IMycState; AProc: TFunc<T>);
begin
inherited Create;
Assert(Assigned(AInitState), 'AInitState must be assigned.');
FDone := TMycLatch.CreateLatch(1);
// Subscribe the job execution to AInitState.
// The job will run when AInitState notifies the subscriber returned by Run.
// AStartCount for Run is 1, meaning it waits for one Notify from AInitState's subscription.
FInit := AInitState.Subscribe(
ATaskFactory.Run(
procedure
begin
try
try
Self.FResult := AProc();
except
Self.FResult := Default(T); // Set result to Default(T) on error
raise; // Re-raise for TaskFactory to handle
end;
finally
Self.FDone.Notify; // Signal that this future is done (successfully or with error)
end;
end
)
);
end;
destructor TMycInitStateFuncFuture<T>.Destroy;
begin
Assert( FInit.State.IsSet );
// Explicitly unsubscribe from the initial state to ensure cleanup.
FInit.Unsubscribe;
inherited Destroy;
end;
function TMycInitStateFuncFuture<T>.GetDone: IMycState;
begin
Result := FDone.State;
end;
function TMycInitStateFuncFuture<T>.GetResult: T;
begin
if not FDone.State.IsSet then
begin
// Design decision: Caller must ensure future is done.
raise Exception.Create('Result is not yet available for the future.');
end;
// Returns FResult. If AProc failed, FResult is Default(T) and the exception
// was raised to be handled by the TaskFactory.
Result := FResult;
end;
end.