【问题标题】:The process won't wait?进程不会等待?
【发布时间】:2019-05-20 17:24:40
【问题描述】:

我使用的代码如下:

 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; 

代码工作,但有时它会触发BidsConversion,但第一个进程仍未完成,因此引发了异常。

为什么应用程序不等待两个进程完成然后触发程序?

【问题讨论】:

  • 返回到 WaitForSingleObject。等待一个过程。然后是另一个。实际上,首先尝试一次只等待一个进程而不阻塞 UI。在你会走路之前不要尝试跑步。
  • 请不要通过删除旧问题并用新代码替换来提出新问题。
  • 您当前无法执行单个进程,并在不阻塞父 UI 的情况下等待它。在你成功完成一个进程之前尝试为两个进程编写代码是不会有成效的。再次。在你可以走路之前不要尝试跑步。将整个问题视为一系列学习的垫脚石。这就是人们学习的方式。
  • 不断告诉你同样的事情而你不断犯同样的错误是令人沮丧的。例如,管道。这是我第四次告诉你,将单个管道的两端连接到单个进程的标准输入和标准输出是错误的。想想看。一个进程在读取它的输入时真的想遇到它自己的输出吗?这对你不起作用的原因是你还没有掌握基础知识。您需要停止尝试解决您的最终目标,并首先学习最终需要的技能。这需要时间。

标签: delphi cmd createprocess delphi-10.3-rio


【解决方案1】:

您没有检查第二个CreateProcess() 的返回值是否失败,但更重要的是您完全误用了MsgWaitForMultipleObjects()

  • 您没有将 两个 进程句柄传递给 MsgWaitForMultipleObjects(),即使您将其 nCount 参数设置为 2。

  • 您正在无条件调用ProcessMessages(),即使MsgWaitForMultipleObjects() 没有告诉您消息正在等待处理。

  • 您的循环的until 子句正在检查错误的终止值,因此您的循环将在任何非超时条件下提前中断,例如:当任一进程完成时,或当消息在排队。

  • 在将 bWaitAll 参数设置为 True 时需要注意一个重要的警告 - 有关详细信息,请参阅 MSDN 上的 MsgWaitForMultipleObjects is a very tricky API

话虽如此,请尝试更多类似的东西:

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;

话虽如此,请考虑在单独的工作线程中异步执行等待,这样您就不会再阻塞主 UI 线程。您可以创建自己的线程来调用WaitForMultipleObjects()(不是MsgWaitForMultipleObjects(),因为您不再需要在消息队列上等待),或者您可以在每个进程句柄上单独使用RegisterWaitForSingleObject()。无论哪种方式,让工作线程在等待完成时通知主 UI 线程,并且在收到两个进程都已完成的通知之前不要调用 BidsConversion()

【讨论】:

  • 非常感谢您的宝贵时间。在if (Running&gt;=..) and (Running = ..) 有警告Comparison always evaluates to TrueComparing signed and unsigned types - widened both operands,目前我没有使用不同的线程,UI 仍然没有阻塞,所以真的需要把它放在 .execute 类中(或者我可能遗漏了什么)?我尝试添加管道以从 cmdline 读取错误消息并更新主要问题中的代码,请查看它是否好。
  • @webs1893 我更新了我的答案以解决警告。但至于您的管道使用情况,它甚至还没有接近正确。 StackOverflow 上有很多关于使用管道进行 Stdin/Stdout 重定向的答案。但是仅在收到窗口消息时执行读取并不理想。如果您改用命名管道,则可以使用重叠 I/O 进行读取,并将重叠事件句柄包含在您等待的句柄数组中,以便在收到读取通知时进行读取。而且你应该使用 2 个管道和 2 个缓冲区,每个进程一组,所以你不要在同一个缓冲区中混合输出
猜你喜欢
  • 2014-11-08
  • 2017-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-07
  • 2011-03-31
相关资源
最近更新 更多