Background Worker

Background Worker abstraction implements a client/server relationship. To create a Background Worker server, call Parallel.BackgroundWorker.



When you call Parallel.BackgroundWorker.Execute, OmniThreadLibrary starts your task in one or more threads. Task code is wrapped in a wrapper which receives requests from the owner, calls the task to process request and returns result to the owner.

See also demo 52_BackgroundWorker.

Example:

Start the worker
FBackgroundWorker := Parallel.BackgroundWorker.NumTasks(2)
  .Execute(
    procedure (const workItem: IOmniWorkItem)
    begin
      workItem.Result := workItem.Data.AsInteger * 3;
    end
  )
  .OnRequestDone(
    procedure (const Sender: IOmniBackgroundWorker;
      const workItem: IOmniWorkItem)
    begin
      lbLogBW.ItemIndex := lbLogBW.Items.Add(Format('%d * 3 = %d',
        [workItem.Data.AsInteger, workItem.Result.AsInteger]));
    end
  );
Schedule a work item
FBackgroundWorker.Schedule(
  FBackgroundWorker.CreateWorkItem(Random(100)));
Stop the worker
FBackgroundWorker.Terminate(INFINITE);
FBackgroundWorker := nil;

Basics

Background worker is designed around the concept of a work item. You create a worker, which spawns one or more background threads, and then schedule work items to it. When they are processed, background worker notifies you so you can process the result. Work items are queued so you can schedule many work items at once and background threads will then process them one by one.

Background worker is created by calling Parallel.BackgroundWorker factory. Usually you'll also set the main work item processing method and completion method by calling Execute and OnRequestDone, respectively. As usual, you can provide OTL with a method, a procedure, or an anonymous method.

FWorker := Parallel.BackgroundWorker
  .OnRequestDone(StringRequestDone)
  .Execute(StringProcessorHL);

To close the background worker, call the Terminate method and set the reference (FWorker) to nil.

To create a work item, call the CreateWorkItem factory and pass the result to the Schedule method. You can pass any data to the work item by passing a parameter to the CreateWorkItem method. If you have to pass multiple parameters, you can collect them in a record and wrap it with a TOmniValue.FromRecord<T>, pass them as an array of TOmniValues or pass them as an object or an interface.

IOmniBackgroundWorker Interface

type
  TOmniTaskInitializerDelegate = 
    reference to procedure(var taskState: TOmniValue);
  TOmniTaskFinalizerDelegate = 
    reference to procedure(const taskState: TOmniValue);
 
  IOmniBackgroundWorker = interface
    function  CreateWorkItem(const data: TOmniValue): IOmniWorkItem;
    procedure CancelAll; overload;
    procedure CancelAll(upToUniqueID: int64); overload;
    function  Config: IOmniWorkItemConfig;
    function  Execute(const aTask: TOmniBackgroundWorkerDelegate = nil): 
      IOmniBackgroundWorker;
    function  Finalize(taskFinalizer: 
      TOmniTaskFinalizerDelegate): IOmniBackgroundWorker;
    function  Initialize(taskInitializer: 
      TOmniTaskInitializerDelegate): IOmniBackgroundWorker;      
    function  NumTasks(numTasks: integer): IOmniBackgroundWorker;
    function  OnRequestDone(const aTask: TOmniWorkItemDoneDelegate): 
      IOmniBackgroundWorker;
    function  OnRequestDone_Asy(const aTask: TOmniWorkItemDoneDelegate): 
      IOmniBackgroundWorker;
    procedure Schedule(const workItem: IOmniWorkItem; 
      const workItemConfig: IOmniWorkItemConfig = nil); 
    function  TaskConfig(const config: IOmniTaskConfig): 
      IOmniBackgroundWorker;
    function  Terminate(maxWait_ms: cardinal): boolean;
    function  WaitFor(maxWait_ms: cardinal): boolean;
  end;

Background worker supports two notification mechanisms. By calling OnRequestDone, you are setting a synchronous handler, which will be executed in the context of the thread that created the background worker (usually a main thread). In other words — if you call OnRequestDone, you don't have to worry about thread synchronisation issues. On the other hand, OnRequestDone_Asy handler is executed asynchronously, in the context of the thread that processed the work item.

By calling NumTasks, you can set the degree of parallelism. By default, background worker uses only one background task but you can override this behaviour.

Calling Terminate will stop background workers. If they stop in maxWait_ms, True will be returned, False otherwise. WaitFor waits for workers to stop (without commanding them to stop beforehand so you would have to call Terminate before WaitFor) and returns True/False just as Terminate does.

Task Initialization

Background worker implements mechanism that can be used by worker tasks to intialize and destroy task-specific structures.

By calling Initialize you can provide a task initializer which is executed once for each worker task before it begins processing work items. Similarly, by calling Finalize you provide the background worker with a task finalizer which is called just before the background task is destroyed.

Both initializer and finalizer will receive a taskState variable where you can store any task-specific data (for example, a class containing multiple task-specific structures). This task state is also available to the work item processor throught the property IOmniWorkItem.TaskState.

Work Item Configuration

You can pass additional configuration parameters to the Schedule method by providing a configuration block, which can be created by calling the Config method. By using this approach, you can set a custom executor method or a custom completion method for each separate work item.

type
  IOmniWorkItemConfig = interface
    function  OnExecute(const aTask: TOmniBackgroundWorkerDelegate): 
      IOmniWorkItemConfig;
    function  OnRequestDone(const aTask: TOmniWorkItemDoneDelegate): 
      IOmniWorkItemConfig;
    function  OnRequestDone_Asy(const aTask: TOmniWorkItemDoneDelegate): 
      IOmniWorkItemConfig;
  end;

Work Item Interface

CreateWorkItem method returns an IOmniWorkItem interface.

type
  IOmniWorkItem = interface ['{3CE2762F-B7A3-4490-BF22-2109C042EAD1}']
    function  DetachException: Exception;
    function  FatalException: Exception;
    function  IsExceptional: boolean;
    property CancellationToken: IOmniCancellationToken
      read GetCancellationToken;
    property Data: TOmniValue read GetData;
    property Result: TOmniValue read GetResult write SetResult;
    property Task: IOmniTask read GetTask;
    property TaskState: TOmniValue read GetTaskState;    
    property UniqueID: int64 read GetUniqueID;
  end;

It contains input data (Data property), result (Result property) and a unique ID, which is assigned in the CreateWorkItem call. First work item gets ID 1, second ID 2 and so on. This allows for some flexibility when you want to cancel work items. You can cancel one specific item by calling workItem.CancellationToken.Signal or multiple items by calling backgroundWorker.CancelAll( highestIDToBeCancelled) or all items by calling backgroundWorker.CancelAll.

Cancellation is partly automatic and partly cooperative. If the work item that is to be cancelled has not yet reached the execution, the system will prevent it from ever being executed. If, however, work item is already being processed, your code must occasionally check workItem.CancellationToken.IsSignalled and exit if that happens (provided that you want to support cancellation at all). Regardless of how the work item was cancelled, completion handler will still be called and it can check workItem.CancellationToken.IsSignalled to check whether the work item was cancelled prematurely or not.

Any uncaught exception will be stored in the FatalException property. You can detach (and take ownership of) the exception by calling the DetachException and you can test if there was an exception by calling IsExceptional. If IsExceptional returns True, any access to the Result property will raise exception stored in the FatalException property. [In other words — if an unhandled exception occurs in the executor code (in the background thread), it will propagate to the place where you access workItem.Result.]

Property Task provides access to the task executing the work item. Property TaskState returns the value initialized in the task initializer.

Examples

Practical examples of Background Worker usage can be found in chapters Background Worker and List Partitioning and OmniThreadLibrary and Databases.

book/highlevel/backgroundworker.txt · Last modified: 2012/11/14 11:55 by gabr
Recent changes RSS feed Debian Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki