multithreadingdelphiomnithreadlibrary

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


Solution

  • 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.