webs1893
webs1893

Reputation: 27

The process won't wait?

the code i use is as below:

 begin
     saSecurity.nLength := SizeOf(TSecurityAttributes);
     saSecurity.bInheritHandle := True;
     saSecurity.lpSecurityDescriptor := nil;
     FillChar(suiStartup, SizeOf(TStartupInfo), #0);
     suiStartup.cb := SizeOf(TStartupInfo);
     suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
     suiStartup.wShowWindow := SW_HIDE;

     ccOk:=CreateProcess(nil, PChar(ExecutableFirst+' '+CommandsFirst),@saSecurity,@saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess);

      if ccOk then
       begin
         CreateProcess(nil, PChar(ExecutableSecond + ' ' + CommandsSecond), @saSecurity,@saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess);
           try
            repeat                 Running:=MsgWaitForMultipleObjects(2,piProcess.hProcess,True,100,QS_ALLINPUT);
             Application.ProcessMessages;
             until Running <> WAIT_TIMEOUT
           finally
            CloseHandle(piProcess.hProcess);
            CloseHandle(piProcess.hThread);
            if (Running=WAIT_OBJECT_0) then BidsConversion; //run this when both process has finished
           end;
       end else    
       begin
       raise.Exception(GetLastError.toString);
        Exit
       end;
 end; 

The Code working but sometimes it is firing BidsConversion however the First Process is still not completed hence it Exception raised.

Why the app is not waiting for both processes to finish then fire the procedure ?

Upvotes: 1

Views: 217

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595887

You are not checking the return value of the 2nd CreateProcess() to see if it failed, but more importantly you are completely misusing MsgWaitForMultipleObjects():

  • you are not passing both process handles to MsgWaitForMultipleObjects() even though you are setting its nCount parameter to 2.

  • you are calling ProcessMessages() unconditionally, even when MsgWaitForMultipleObjects() does not tell you that messages are waiting to be processed.

  • your loop's until clause is checking for the wrong termination value, so your loop will break prematurely on ANY condition that is not a timeout, eg: when either process finishes, or when a message is pending in the queue.

  • there is an important caveat with setting the bWaitAll parameter to True that you need to be aware of - see MsgWaitForMultipleObjects is a very tricky API on MSDN for details about that.

With that said, try something more like this:

var
  ...
  arrHandles: array[0..1] of THandle;
  numHandles, i: Integer;
begin
  ...

  ccOk := CreateProcess(nil, PChar(ExecutableFirst + ' ' + CommandsFirst), @saSecurity, @saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess);
  if not ccOk then
    RaiseLastOSError;

  CloseHandle(piProcess.hThread);
  arrHandles[0] := piProcess.hProcess;
  numHandles := 1;

  try
    ccOk := CreateProcess(nil, PChar(ExecutableSecond + ' ' + CommandsSecond), @saSecurity, @saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess);
    if not ccOk then
      RaiseLastOSError;

    CloseHandle(piProcess.hThread);
    arrHandles[1] := piProcess.hProcess;
    numHandles := 2;

    // there is a caveat when setting bWaitAll=True that the wait will not be
    // completely satisfied until both handles are signaled AND the calling thread
    // receives an input event!  That last caveat is not desirable, so setting
    // bWaitAll=False instead to avoid that so the loop can break immediately when
    // both handles are signaled...
    repeat
      Running := MsgWaitForMultipleObjects(numHandles, arrHandles, False, INFINTE, QS_ALLINPUT);
      if {(Running >= WAIT_OBJECT_0) and} (Running < (WAIT_OBJECT_0 + DWORD(numHandles))) then
      begin
        i := Integer(Running - WAIT_OBJECT_0);
        CloseHandle(arrHandles[i]);
        if i = 0 then arrHandles[0] := arrHandles[1];
        Dec(numHandles);
      end
      else if Running = (WAIT_OBJECT_0 + DWORD(numHandles)) then begin
        Application.ProcessMessages;
      end
      else if Running = WAIT_FAILED then begin
        RaiseLastOSError;
      end;
    until numHandles = 0;
  except
    for i := 0 to numHandles-1 do begin
      TerminateProcess(arrHandles[i], 0);
      CloseHandle(arrHandles[i]);
    end;
    raise;
  end;

  BidsConversion; //run this when both processes have finished without error

  ...
end;

That being said, consider doing the wait asynchronously in a separate worker thread so you are not blocking the main UI thread anymore. You can create your own thread that calls WaitForMultipleObjects() (not MsgWaitForMultipleObjects() since you would not need to wait on the message queue anymore), or you can use RegisterWaitForSingleObject() on each process handle individually. Either way, let the worker thread(s) notify the main UI thread when waiting is finished, and just don't call BidsConversion() until you have received notification that both processes have finished.

Upvotes: 3

Related Questions