【问题标题】:Batch script calling robocopy in Process won't terminate在 Process 中调用 robocopy 的批处理脚本不会终止
【发布时间】:2017-06-23 02:58:05
【问题描述】:

如果从另一个线程甚至另一个程序调用process.Kill(),如果批处理脚本使用robocopy.exe,则该进程永远不会退出WaitForExit(),直到它完成,就好像它没有被杀死一样。

从批处理脚本调用 Robocopy.exe。其他所有脚本或程序都按您的预期结束。

    ProcessStartInfo startInfo = new ProcessStartInfo();
    startInfo.FileName = "batch.bat";
    startInfo.UseShellExecute = false;
    startInfo.CreateNoWindow = true;
    startInfo.RedirectStandardOutput = true;
    startInfo.OutputDataReceived += CaptureHandler;
    startInfo.RedirectStandardError = true;
    startInfo.ErrorDataReceived += CaptureHandler;
    process.Start();
    process.BeginOutputReadLine();
    process.BeginErrorReadLine();
    process.WaitForExit(); 

批处理脚本如下所示:

@echo off
call "robocopy.exe" "somedir" "somedest" /mir /fp /ndl /njh /njs /ns

我感觉它与输出处理程序有关。 我尝试在Kill() 调用之后和之前使用process.CancelErrorReadprocess.CancelOutputRead(),但没有运气。

奇怪的是,如果你使用process.WaitForExit(timeout) 重载,它会在另一个线程的Kill() 之后立即返回true。然而,它在撒谎。该过程仍在运行!如果您再次尝试process.WaitForExit(),按照 MSDN 文档,尽管HasExited 说是真的,它仍会等待该过程完成。

为确保异步事件处理已完成,请在从该重载接收到 true 后调用不带参数的 WaitForExit() 重载。

https://msdn.microsoft.com/en-us/library/ty0d8k56(v=vs.110).aspx

【问题讨论】:

  • 你可以在最后尝试process.Close(),或者如果你不需要重定向输出和错误(以防robocopy.exe处理它)你可以简单地使用System.Diagnostics.Process.Start(@"c:\batch.bat");(更正根据需要的路径),因为它只会打开批处理文件并退出该过程。
  • Close 也不起作用。我需要等待它。

标签: c# .net windows batch-file robocopy


【解决方案1】:

您已成功终止批处理器 (cmd.exe),但这样做不会终止 robocopy,它是一个单独的进程。

doesn't seem to be documented,when we look at the .NET source code 原来Process.WaitForExit() 方法不只是等待进程退出,它等待结束-of-file 在标准输出和标准错误流上。在这种情况下,这意味着它会等待 robocopy 完成,即使批处理程序已被杀死。

Process.WaitForExit 超时的重载没有这个额外的逻辑。)

我认为这构成了 .NET 框架中的一个错误。至少应该记录在案。

作为一种解决方法,您可以使用.HasExited 和/或WaitForExit 的版本来确定进程是否已退出。当然,在您的场景中,您可能更喜欢等待孙进程,在这种情况下,您的代码已经按预期运行。

【讨论】:

  • 那么有没有办法在不知道正在使用 robocopy 的情况下从应用程序中杀死 robocopy?应用程序不必知道批处理脚本正在启动另一个进程,我希望 kill 能够确定所有子进程。我想也许我可以先遍历子树并将其从下到上杀死?
  • 不平凡;默认情况下,Windows 不会跟踪该信息。最强大的方法是启动批处理脚本inside a job 虽然我不知道.NET 是否支持它,但您可能需要 P/Invoke。一个更混乱但更简单的选择是在一个单独的控制台中启动它,并带有一个独特的标题,and kill all the processes belonging to that console
  • 应用程序不必知道批处理脚本正在启动另一个进程 - Windows 并不是真正为这种方法设计的;启动一个批处理脚本来代表您完成工作是非常 UNIX 的。当然,您可以做到这一点,Windows 并没有竭尽全力让它变得简单。
【解决方案2】:

我遇到了同样的问题。就我而言,从 RoboCopy 参数列表中删除 /mt 开关似乎可以解决问题。

【讨论】:

    【解决方案3】:

    跟进Harry Johnston's helpful answer,我发现当你避开RedirectStandardOutput = true时该过程正常完成。如果这不是一个可接受的解决方案,我发现使用 robocopy 的/LOG:"C:\logs\robocopy.txt" 开关将其标准输出发送到外部日志文件也可以(尽管您失去了从进程对象本身获取文件/目录日志输出的能力)。

    【讨论】:

      【解决方案4】:

      现在看来,在应用程序不知道要终止 Robocopy.exe 的情况下,唯一的方法是在终止脚本本身之前终止脚本进程的子进程:

      Kill process tree programmatically in C#

          /// <summary>
          /// Kill a process, and all of its children, grandchildren, etc.
          /// </summary>
          /// <param name="pid">Process ID.</param>
          private static void KillProcessAndChildren(int pid)
          {
              ManagementObjectSearcher searcher = new ManagementObjectSearcher
                ("Select * From Win32_Process Where ParentProcessID=" + pid);
              ManagementObjectCollection moc = searcher.Get();
              foreach (ManagementObject mo in moc)
              {
                  KillProcessAndChildren(Convert.ToInt32(mo["ProcessID"]));
              }
              try
              {
                  Process proc = Process.GetProcessById(pid);
                  proc.Kill();
              }
              catch (ArgumentException)
              {
                  // Process already exited.
              }
          }
      

      【讨论】:

      • 这是一种危险的做法;它可能会杀死不相关的进程。 This answer 更好。
      猜你喜欢
      • 2018-04-13
      • 1970-01-01
      • 2011-06-11
      • 2012-11-17
      • 2015-05-08
      • 2017-04-22
      • 2013-11-01
      • 1970-01-01
      • 2018-06-07
      相关资源
      最近更新 更多