Friday, July 04, 2008

OmniThreadLibrary internals - OtlTask

image [Please note - new snapshot is available on the OTL home page.]

In next few posts, I'll try to present a rough overview of the OTL internals.

The most important unit is OtlTask. It hosts interfaces describing threaded task. Currently, it also provides thread descendant to run those tasks, but that one will most probably be moved to the OtlThread unit.

The most important interface in OtlTask is IOmniTaskControl. This task control interface is returned from the CreateTask function. It is used to communicate with the task and to control its behavior. Four CreateTask functions map directly to four constructors, which store execution parameters into internal field and initialize some events used for task control.

    constructor Create(worker: IOmniWorker; const taskName: string); overload;
constructor Create(worker: TOmniWorker; const taskName: string); overload;
constructor Create(executor: TOmniTaskMethod; const taskName: string); overload;
constructor Create(executor: TOmniTaskProcedure; const taskName: string); overload;

SetParameter* methods are used to set parameters that are passed to the threaded worker.

    function  SetParameter(const paramName: string; paramValue: TOmniValue): IOmniTaskControl; overload;
function SetParameter(paramValue: TOmniValue): IOmniTaskControl; overload;
function SetParameters(parameters: array of TOmniValue): IOmniTaskControl;

Next four methods are used to control internal event/message loop behavior. They are only used when working with IOmniWorker or TOmniWorker tasks. Alertable causes message wait to be alertable and MsgWait causes message wait to process messages (see TOmniTaskControl.DispatchMessages for more information). SetTimer causes timerMessage message to be sent to the worker in specified intervals. If timerMessage is -1, worker's Timer method is called instead. FreeOnTerminate is only available when worker is a descendant of the TOmniWorker class and causes the worker to be destroyed after the task is completed.

    function  Alertable: IOmniTaskControl;
function FreeOnTerminate: IOmniTaskControl;
function MsgWait(wakeMask: DWORD = QS_ALLEVENTS): IOmniTaskControl;
function SetTimer(interval_ms: cardinal; timerMessage: integer = -1): IOmniTaskControl;

SetMonitor and RemoveMonitor are used to attach external message watching monitor. Typically that would be the TOmniTaskEventDispatch component and those TOmniTaskControl messages would be called component's Monitor and Detach functions.

    function  RemoveMonitor: IOmniTaskControl;
function SetMonitor(hWindow: THandle): IOmniTaskControl;

To start the task, you must execute either Run (creates a new thread and starts executing the task) or Schedule (schedules task to be executed in a thread pool; not implemented yet).

    function  Run: IOmniTaskControl;
function Schedule(threadPool: IOmniThreadPool = nil {default pool}): IOmniTaskControl;

There are four other methods related to the execution control. Terminate stops the task and waits the specified amount of time for the task to complete gracefully. At the moment it returns False if task does not terminate in the allotted time. In the future, it will maybe kill the thread. I'm not sure yet.


TerminateWhen is not implemented yet. It was supposed to assign another synchronization object to the thread. That object would cause the task to stop. The idea was to use one event to stop multiple tasks at once, but I'm not sure if that is useful at all.


WaitFor waits for the task to finish execution and returns True if task has stopped. WaitForInit waits for IOmniWorker/TOmniWorker worker to execute its Initialize method and returns initialization status (return value of the Initialize method).

    function  Terminate(maxWait_ms: cardinal = INFINITE): boolean;
function TerminateWhen(event: THandle): IOmniTaskControl;
function WaitFor(maxWait_ms: cardinal): boolean;
function WaitForInit: boolean;

The Comm property gives the owner access to the communication subsystem. This will be covered in a separate post.


ExitCode and ExitMessage will return task's exit code and message. Not implemented yet.


Name returns task's name, as set in the constructor and UniqueID returns task's unique ID. This identifier is unique inside the application.


Options gives you direct access to execution options, usually set with Alertable, MsgWait, and FreeOnTerminate.

    property Comm: IOmniCommunicationEndpoint read GetComm;
property ExitCode: integer read GetExitCode;
property ExitMessage: string read GetExitMessage;
property Name: string read GetName;
property Options: TOmniTaskControlOptions read otcOptions write otcOptions;
property UniqueID: cardinal read GetUniqueID;

Execution


Run first makes parameters immutable, then creates thread-side task interface (IOmniTask), creates new thread, assigns that task to the thread and starts the thread.

function TOmniTaskControl.Run: IOmniTaskControl;
var
task: IOmniTask;
begin
otcParameters.Lock;
task := TOmniTask.Create(otcExecutor, otcTaskName, otcParameters, otcCommChannel,
otcUniqueID, otcTerminateEvent, otcTerminatedEvent, otcMonitorWindow);
otcThread := TOmniThread.Create(task);
otcThread.Resume;
Result := Self;
end; { TOmniTaskControl.Run }

Thread's Execute method passes control to the TOmniTaskControl's Execute method.

procedure TOmniThread.Execute;
begin
{$IFNDEF OTL_DontSetThreadName}
SetThreadName(otTask.Name);
{$ENDIF OTL_DontSetThreadName}
(otTask as IOmniTaskExecutor).Execute;
end; { TOmniThread.Execute }

Execute calls either procedure or method worker. When working with IOmniWorker/TOmniWorker worker, Execute calls TOmniTaskControl.DispatchMessages. In all cases, IOmniTask is passed as a parameter.

procedure TOmniTaskExecutor.Execute(task: IOmniTask);
begin
case ExecutorType of
etMethod:
Method(task);
etProcedure:
Proc(task);
else
raise Exception.Create('TOmniTaskExecutor.Execute: Executor is not set');
end;
end; { TOmniTaskExecutor.Execute }

IOmniTask


IOmniTask interface allows the worker to communicate with the owner.

  IOmniTask = interface
procedure SetExitStatus(exitCode: integer; const exitMessage: string);
procedure Terminate;
property Comm: IOmniCommunicationEndpoint read GetComm;
property Name: string read GetName;
property Param[idxParam: integer]: TOmniValue read GetParam;
property ParamByName[const paramName: string]: TOmniValue read GetParamByName;
property TerminateEvent: THandle read GetTerminateEvent;
property UniqueID: cardinal read GetUniqueID;
end; { IOmniTask }

SetExitStatus sets exit code and message, which are then mapped to task control's ExitCode and ExitMessage parameters. Not implemented yet.


Terminate sets task's TerminateEvent. Thus the task can terminate itself. TerminateEvent is also set when code calls task control interface's Terminate method.


Comm returns task's communication endpoint. More details will be provided in the next post.


Name returns the task name, as set in the CreateTask call. UniqueID returns task's unique ID.


Param and ParamByName can be used to access parameters passed to the task.


More information on task writing is available in examples (and already published posts on the OTL topic).

No comments:

Post a Comment