Monday 5 December 2016

Low-level Multithreading with OmniThreadLibrary

The low-level OmniThreadLibrary layer focuses on the task concept. In most aspects this is similar to the Delphi’s TThread approach except that OmniThreadLibrary focuses on the code (a.k.a. task) and interaction with the code while the Delphi focuses on the operating system primitive required for executing additional threads (TThread).

A task is created using the CreateTask function, which takes as a parameter a global procedure, a method, an instance of the TOmniWorker class (or, usually, a descendant of that class) or an anonymous procedure (in Delphi 2009 and newer). CreateTask will also accept an optional second parameter, a task name, which will be displayed in the Delphi’s Thread view on the thread running the task.

type
 TOmniTaskProcedure = procedure(const task: IOmniTask);
  TOmniTaskMethod = procedure(const task: IOmniTask) of object;
  TOmniTaskDelegate = reference to procedure(const task: IOmniTask);

function CreateTask(worker: TOmniTaskProcedure; const taskName: string = ''): 
  IOmniTaskControl; overload;
function CreateTask(worker: TOmniTaskMethod; const taskName: string = ''): 
  IOmniTaskControl; overload;
function CreateTask(worker: TOmniTaskDelegate; const taskName: string = ''): 
  IOmniTaskControl; overload;
function CreateTask(const worker: IOmniWorker; const taskName: string = ''): 
  IOmniTaskControl; overload; 
CreateTask returns a feature-full interface IOmniTaskControl which we will explore in this chapter. The most important function in this interface, Run, will create a new thread and start your task in it.

Low-level For the Impatient
The following code represents the simplest possible low-level OmniThreadLibrary example. It executes the Beep function in a background thread. The Beep function merely beeps and exits. By exiting from the task function, the Windows thread running the task is also terminated.
procedure TfrmTestSimple.Beep(const task: IOmniTask);
begin
  //Executed in a background thread
  MessageBeep(MB_ICONEXCLAMATION);
end;

CreateTask(Beep, 'Beep').Run;
Another way to start a task is to call a Schedule function which starts it in a thread allocated from a thread pool. This is covered in the Thread Pooling chapter.

Four ways to create a task
Let’s examine all four ways of creating a task. The simplest possible way (demoed in application 2_TwoWayHello) is to pass a name of a global procedure to the CreateTask. This global procedure must consume one parameter of type IOmniTask .
procedure RunHelloWorld(const task: IOmniTask);
begin
  //
end;

CreateTask(RunHelloWorld, 'HelloWorld').Run;
A variation on the theme is passing a name of a method to the CreateTask. This approach is used in the demo application 1_HelloWorld. The interesting point here is that you can declare this method in the same class from which the CreateTask is called. That way you can access all class fields and methods from the threaded code. Just keep in mind that you’ll be doing this from another thread so make sure you know what you’re doing!
procedure TfrmTestHelloWorld.RunHelloWorld(const task: IOmniTask);
begin
  //
end;

procedure TfrmTestHelloWorld.StartTask;
begin
  CreateTask(RunHelloWorld, 'HelloWorld').Run;
end;
In Delphi 2009 and newer you can also write the task code as an anonymous function.
CreateTask(
procedure (const task: IOmniTask)
begin
  //
end,
'HellowWorld').Run;
For all except the simplest tasks, you’ll use the fourth approach as it will give you access to the true OmniThreadLibrary power (namely internal wait loop and message dispatching). To use it, you have to create a worker object deriving from the TOmniWorker class.
type
  THelloWorker = class(TOmniWorker)
  end;

procedure TfrmTestTwoWayHello.actStartHelloExecute(Sender: TObject);
begin
  FHelloTask :=
    CreateTask(THelloWorker.Create(), 'Hello').
    Run;
end;
IOmniTaskControl and IOmniTask Interfaces
When you create a low-level task, OmniThreadLibrary returns a task controller interface IOmniTaskControl. This interface, which is defined in the OtlTaskControl unit, can be used to control the task from the owner’s side. The task code, on the other hand, has access to another interface, IOmniTask (defined in the OtlTask unit), which can be used to communicate with the owner and manipulate the task itself. A picture in the Tasks vs. Threads chapter shows the relationship between those interfaces.

This chapter deals mainly with these two interfaces. For the reference reasons, the IOmniTaskControl is reprinted here in full. In the rest of the chapter I’ll just show relevant interface parts.

The IOmniTask interface is described at the end of this chapter.
type
IOmniTaskControl = interface 
 function  Alertable: IOmniTaskControl;
 function  CancelWith(const token: IOmniCancellationToken): IOmniTaskControl;
 function  ChainTo(const task: IOmniTaskControl;
   ignoreErrors: boolean = false): IOmniTaskControl;
 function  ClearTimer(timerID: integer): IOmniTaskControl;
 function  DetachException: Exception;
 function  Enforced(forceExecution: boolean = true): IOmniTaskControl;
 function  GetFatalException: Exception;
 function  GetParam: TOmniValueContainer;
 function  Invoke(const msgMethod: pointer): IOmniTaskControl; overload;
 function  Invoke(const msgMethod: pointer; 
   msgData: array of const): IOmniTaskControl; overload;
 function  Invoke(const msgMethod: pointer; 
   msgData: TOmniValue): IOmniTaskControl; overload;
 function  Invoke(const msgName: string): IOmniTaskControl; overload;
 function  Invoke(const msgName: string; 
   msgData: array of const): IOmniTaskControl; overload;
 function  Invoke(const msgName: string; 
   msgData: TOmniValue): IOmniTaskControl; overload;
 function  Invoke(remoteFunc: TOmniTaskControlInvokeFunction):
   IOmniTaskControl; overload;
 function  Invoke(remoteFunc: TOmniTaskControlInvokeFunctionEx):
   IOmniTaskControl; overload;
 function  Join(const group: IOmniTaskGroup): IOmniTaskControl;
 function  Leave(const group: IOmniTaskGroup): IOmniTaskControl;
 function  MonitorWith(const monitor: IOmniTaskControlMonitor): 
   IOmniTaskControl;
 function  MsgWait(wakeMask: DWORD = QS_ALLEVENTS): IOmniTaskControl;
 function  OnMessage(eventDispatcher: TObject): IOmniTaskControl; overload;
function  OnMessage(eventHandler: TOmniTaskMessageEvent): IOmniTaskControl; overload;
function  OnMessage(msgID: word; eventHandler: TOmniTaskMessageEvent): 
  IOmniTaskControl; overload;
function  OnMessage(msgID: word; eventHandler: TOmniMessageExec): 
  IOmniTaskControl; overload;
function  OnMessage(eventHandler: TOmniOnMessageFunction): 
  IOmniTaskControl; overload;
function  OnMessage(msgID: word; eventHandler: TOmniOnMessageFunction):
  IOmniTaskControl; overload;
function  OnTerminated(eventHandler: TOmniOnTerminatedFunction):
  IOmniTaskControl; overload;
function  OnTerminated(eventHandler: TOmniOnTerminatedFunctionSimple): 
  IOmniTaskControl; overload;
function  OnTerminated(eventHandler: TOmniTaskTerminatedEvent): 
  IOmniTaskControl; overload;
function  RemoveMonitor: IOmniTaskControl;
function  Run: IOmniTaskControl;
function  Schedule(const threadPool: IOmniThreadPool = nil {default pool}):
  IOmniTaskControl;
function  SetMonitor(hWindow: THandle): IOmniTaskControl;
function  SetParameter(const paramName: string; 
  const paramValue: TOmniValue): IOmniTaskControl; overload;
function  SetParameter(const paramValue: TOmniValue): 
  IOmniTaskControl; overload;
function  SetParameters(const parameters: array of TOmniValue):
  IOmniTaskControl;
function  SetPriority(threadPriority: TOTLThreadPriority): IOmniTaskControl;
function  SetQueueSize(numMessages: integer): IOmniTaskControl;
function  SetTimer(timerID: integer; interval_ms: cardinal; 
  const timerMessage: TOmniMessageID): IOmniTaskControl; overload;
function  SetUserData(const idxData: TOmniValue; 
  const value: TOmniValue): IOmniTaskControl;
procedure Stop;
function  Terminate(maxWait_ms: cardinal = INFINITE): boolean; 
function  TerminateWhen(event: THandle): IOmniTaskControl; overload;
function  TerminateWhen(token: IOmniCancellationToken): 
  IOmniTaskControl; overload;
function  Unobserved: IOmniTaskControl;
function  WaitFor(maxWait_ms: cardinal): boolean;
function  WaitForInit: boolean;
function  WithCounter(const counter: IOmniCounter): IOmniTaskControl;
function  WithLock(const lock: TSynchroObject; 
  autoDestroyLock: boolean = true): IOmniTaskControl; overload;
 function  WithLock(const lock: IOmniCriticalSection): IOmniTaskControl; overload;
  property CancellationToken: IOmniCancellationToken 
    read GetCancellationToken;
  property Comm: IOmniCommunicationEndpoint read GetComm;
  property ExitCode: integer read GetExitCode;
  property ExitMessage: string read GetExitMessage;
  property FatalException: Exception read GetFatalException;
  property Lock: TSynchroObject read GetLock;
  property Name: string read GetName;
  property Param: TOmniValueContainer read GetParam;
  property UniqueID: int64 read GetUniqueID;
  property UserData[const idxData: TOmniValue]: TOmniValue 
    read GetUserDataVal write SetUserDataVal;
end;

Copied from: https://leanpub.com/omnithreadlibrary




0 comments:

Post a Comment