3

I've created an IOmniTaskControl using a TOmniWorker, so that I can periodically run chunks of code an a specific thread. So I'll be calling Invoke as needed on this IOmniTaskControl. When I do so, I will sometimes need to wait for the execution associated with that work to complete.

I tried calling WaitFor(INFINITE) on the IOmniTaskControl after the Invoke, but it hangs. I debugged and could see the method on the Invoke call completed. Am I misunderstanding the use of WaitFor with IOmniTaskControl when used with a TOmniWorker?

Edit: I edited up the test_43_InvokeAnonymous project in the test folder and I see the same behavior:

procedure TfrmInvokeAnonymousDemo.btnInvokeClick(Sender: TObject);
var
  formThreadID: DWORD;
  task        : IOmniTaskControl;
begin
  formThreadID := GetCurrentThreadID;
  if Sender = btnInvoke then
    task := FTask
  else
    task := FMonitoredTask;

  task.Invoke(
    procedure (const task: IOmniTask)
    var
      taskThreadID: DWORD;
    begin
      // this will execute in the context of the worker thread
      taskThreadID := GetCurrentThreadID;
      Sleep(2000);
//      task.Invoke(
//        procedure
//        begin
//          // this will execute in the context of the main thread
//          frmInvokeAnonymousDemo.lbLog.Items.Add(Format(
//            'Current thread ID: %d, task thread ID: %d, ' +
//            ' form thread ID: %d',
//            [GetCurrentThreadID, taskThreadID, formThreadID]));
//        end
//      );
    end
  );

  task.WaitFor(INFINITE);
  frmInvokeAnonymousDemo.lbLog.Items.Add('Done waiting.');
end;

With the above code, the task.WaitFor(INFINITE); hangs.

Larry Fuqua
  • 148
  • 1
  • 5

1 Answers1

5

WaitFor waits for the task to finish execution but as nobody told the task to terminate, it will wait forever.

Scheduling a background operation and then waiting for it more or less defeats the purpose of a background execution. It is better to schedule a background task and then send a notification back to the main thread when the task is finished. Use any variation of OnMessage (in the main thread) and Task.Comm.Send (in the worker).

Even better way would be to use Parallel.Future, which encapsulates exactly what you want to do.

If you really have to wait on the Invoke to complete, you can create a waitable value before the Invoke, signal it in the code executed by the Invoke and wait for it in the code that called the Invoke.

gabr
  • 26,580
  • 9
  • 75
  • 141
  • Well in this case I'm stepping into the middle between two libraries in an existing architecture. Plus I'm needing to ensure that all the calls going to one end get funneled onto the same thread (for COM reasons), even though calls from the other end will be coming on multiple different threads (though none concurrently). So with Parallel.Future, will I be able to control what thread I'm using to push the work onto? So that the next time I need to push work onto that same thread, the same Parallel.Future will let me do so? – Larry Fuqua Mar 17 '16 at 10:13
  • No, Parallel.Future takes a thread from a pool so you have no assurance on which thread will be used when you start a task. – gabr Mar 17 '16 at 10:19
  • So best approach is to use the TOmniWorker/IOmniTaskControl? What is the best technique for synchronizing on the Invoke completion? Some sort of event or wait object? I definitely need to block the original thread until it's done. – Larry Fuqua Mar 17 '16 at 11:59
  • Use a waitable value. (See the answer above.) – gabr Mar 17 '16 at 12:04