Reputation: 27
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
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