H.Hasenack
H.Hasenack

Reputation: 1164

Omnithread: Create a task wrapper / modify a task that adds some extra pre- and post processing to an alredy existing task

Some background:

Basically it comes down to that I want to be able to "execute" the task in the current thread. Why? -I have a task creator routine, and one time I want the task to be executed immediately in a background task, and at other times I want the task to be scheduled using an IOmniThreadPool.

In the case that I want to use the OmniThreadpool I want do do some output about the task's status, like that it was queued, started, finished or failed.

The task's execution is not yet started at this moment.

So I imagined someting like this:

function WrapIntoPoolTask(const aOriginalTask:IOmniTaskCOntrol):IOmniTaskCOntrol;
begin
  if Assigned(aOriginalTask) then
  begin
    var lPoolProgress:=TaskPoolProgress; // fetch global task pool progress interface
    // immediately displays message says its been queued, remember message bookmark
    var lMessageBookMark:=lPoolProgress.MessageList.AddMessage(aOriginalTask.Name,pmPaused);
    Result:=CreateTask(
      procedure (const aTask:IOmniTask)
      begin
        lPoolProgress.MessageList.UpdateMessage(lMessageBookMark,pmStarted); // update message status
        try

          aOriginalTask.ExecuteTaskInThisThread; // <<=== So how do I do this?

          lPoolProgress.MessageList.UpdateMessage(lMessageBookMark,pmCompleted);
        except
          lPoolProgress.MessageList.UpdateMessage(lMessageBookMark,pmFailed);
          raise;
        end;
      end
      ,
      'Pooled:'+aOriginalTask.Name
    );
  end;
end;

Using the UpdateMessage call after performing the original task can be moved to the OnTerminated handler of the IOmniTaskControl interface. I tried that and it works just fine for the thread ending part. It even allows for handling exit codes and exit messages which I like even better.

I think what I am missing here is probably an OnInitialize or OnStartExecution handler to set my pmStarted status.

Question:

or

Upvotes: 3

Views: 211

Answers (1)

H.Hasenack
H.Hasenack

Reputation: 1164

In order to solve my problem I had to change the omnithreadlibrary unit OtlTaskControl a bit.

one routine added to IOmniTaskControl (GUID should change, but I didn'tt)

  IOmniTaskControl = interface ['{881E94CB-8C36-4CE7-9B31-C24FD8A07555}']
...
    function DirectExecute:IOmniTaskControl;
...
  end; { IOmniTaskControl }

And the implementation added to TOmniTaskControl:

function TOmniTaskControl.DirectExecute:IOmniTaskControl;
VAR lTask:IOmniTask;
begin
  Result:=self;
  lTask:=CreateTask;
  (lTask as IOmniTaskExecutor).Execute;
end;

Then my "custom wrapper" routine that actually adds the pool progress handling to whatever the original task was:

function WrapIntoOmniPoolTask(const aTaskControl:IOmniTaskCOntrol):IOmniTaskCOntrol;
var lTaskControl:IOmniTaskCOntrol;
    lPoolProgress:IAppProgress;
    lmbm:integer;
begin
  if Assigned(aTaskControl) then
  begin
    // have some local copies to work around compiler bug RX10.3 and RX10.4
    // cannot use inline vars due to this bug either.
    lTaskControl:=aTaskControl;
    lPoolProgress:=TaskPoolProgress;
    lmbm:=lPoolProgress.MessageList.AddMessage(aTaskControl.Name,pmPaused);
    Result:=CreateTask(
      procedure (const aTask:IOmniTask)
      begin
        try
          lPoolProgress.MessageList.UpdateMessage(lmbm,pmStarted);
          try
            lTaskControl.DirectExecute;
            aTask.SetExitStatus(lTaskControl.ExitCode,lTaskControl.ExitMessage);
            HandlePoolTaskTermination(lTaskControl,lmbm,lPoolProgress);
          except
            HandlePoolTaskTermination(lTaskControl,lmbm,lPoolProgress);
            if IsFatalException then
              raise;
          end;
        finally
          // release interfaces when all is done
          lPoolProgress:=nil; 
          lTaskControl:=nil;
        end;
      end,
      'Pooled: '+lTaskCOntrol.Name
    );
  end;
end;

And finally, the routine that schedules my wrapped task into the omnipoolthread.

function TfrmMain.CreateTestTask:IOmniTaskControl;
begin
  Result:=WrapIntoOmniPoolTask(CreateTask(TestTask,TGUID.NewGuid.ToString)).Unobserved;
end;

Everything seems to work as expected including the exit code and exit message which are propagated from the inner task to the outer task.

The compiler bug I am referring to is reported here: https://quality.embarcadero.com/browse/RSP-29564 (please vote!)


For those interested: this is what HandlePoolTaskTermination looks like:

procedure HandlePoolTaskTermination(const aTaskControl:IOmniTaskCOntrol;const aMessageBookmark:integer;const aPoolProgress:IAppProgress);
begin
  var pm:=pmCompleted;
  if Assigned(aTaskControl.FatalException) then
  begin
    pm:=pmWarning;
    var pe:=peError;
    if IsAbortException(aTaskControl.FatalException) then
      pe:=peWarning
    else if IsFatalException(aTaskControl.FatalException) then
    begin
      pm:=pmFailed;
      pe:=peFatalError;
    end;
    aPoolProgress.ErrorList.AddErrorToMessage(aMessageBookmark,'',pe,aTaskControl.FatalException)
  end
  else if aTaskControl.ExitCode<>0 then
  begin
    pm:=pmWarning;
    aPoolProgress.ErrorList.AddErrorToMessage(aMessageBookmark,aTaskControl.ExitMessage,peWarning);
  end;
  aPoolProgress.MessageList.UpdateMessage(aMessageBookmark,pm);
end;

The IsFatalException returns true if the "current" exception is eg EAccessViolation, EInvalidOperation and alike.

Upvotes: 4

Related Questions