在 Process 中调用 robocopy 的批处理脚本不会终止
Batch script calling robocopy in Process won't terminate
如果从另一个线程甚至另一个程序调用 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.CancelErrorRead
和 process.CancelOutputRead()
,但没有成功。
奇怪的是,如果您使用 process.WaitForExit(timeout)
重载,它会在来自其他线程的 Kill()
之后立即 return 为真。然而,这是在说谎。进程依旧运行!如果您再次尝试 process.WaitForExit()
,根据 MSDN 文档,它仍将等待进程完成,尽管 HasExited
为真。
To ensure that asynchronous event handling has been completed, call the WaitForExit() overload that takes no parameter after receiving a true from this overload.
https://msdn.microsoft.com/en-us/library/ty0d8k56(v=vs.110).aspx
您已成功终止批处理程序 (cmd.exe),但这样做不会终止 robocopy,这是一个单独的进程。
它 doesn't seem to be documented, but when we look at the .NET source code 事实证明 Process.WaitForExit()
方法不 只是 等待进程退出,它 也 等待标准输出和标准错误流的文件结束。在这种情况下,这意味着它等待 robocopy 完成,即使在批处理器已被终止后也是如此。
(带超时的Process.WaitForExit
的重载没有这个额外的逻辑。)
我认为这构成了 .NET 框架中的错误。至少,它应该被记录下来。
作为变通方法,您可以使用 .HasExited
and/or 版本 WaitForExit
的超时来确定进程是否已退出。当然,在您的场景中,您可能 更喜欢 等待孙子进程,在这种情况下,您的代码已经按预期运行。
现在看来,在应用程序不知道终止的情况下执行此操作的唯一方法 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.
}
}
我运行陷入同样的问题。就我而言,从 RoboCopy 参数列表中删除 /mt
开关似乎可以解决问题。
在跟进 后,我发现当您避免 RedirectStandardOutput = true
时,该过程可以正常完成。如果这不是一个可接受的解决方案,我发现使用 robocopy 的 /LOG:"C:\logs\robocopy.txt"
开关将其标准输出发送到外部日志文件也可以(尽管您失去了从进程中获取 file/directory 日志输出的能力对象本身)。
如果从另一个线程甚至另一个程序调用 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.CancelErrorRead
和 process.CancelOutputRead()
,但没有成功。
奇怪的是,如果您使用 process.WaitForExit(timeout)
重载,它会在来自其他线程的 Kill()
之后立即 return 为真。然而,这是在说谎。进程依旧运行!如果您再次尝试 process.WaitForExit()
,根据 MSDN 文档,它仍将等待进程完成,尽管 HasExited
为真。
To ensure that asynchronous event handling has been completed, call the WaitForExit() overload that takes no parameter after receiving a true from this overload.
https://msdn.microsoft.com/en-us/library/ty0d8k56(v=vs.110).aspx
您已成功终止批处理程序 (cmd.exe),但这样做不会终止 robocopy,这是一个单独的进程。
它 doesn't seem to be documented, but when we look at the .NET source code 事实证明 Process.WaitForExit()
方法不 只是 等待进程退出,它 也 等待标准输出和标准错误流的文件结束。在这种情况下,这意味着它等待 robocopy 完成,即使在批处理器已被终止后也是如此。
(带超时的Process.WaitForExit
的重载没有这个额外的逻辑。)
我认为这构成了 .NET 框架中的错误。至少,它应该被记录下来。
作为变通方法,您可以使用 .HasExited
and/or 版本 WaitForExit
的超时来确定进程是否已退出。当然,在您的场景中,您可能 更喜欢 等待孙子进程,在这种情况下,您的代码已经按预期运行。
现在看来,在应用程序不知道终止的情况下执行此操作的唯一方法 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.
}
}
我运行陷入同样的问题。就我而言,从 RoboCopy 参数列表中删除 /mt
开关似乎可以解决问题。
在跟进 RedirectStandardOutput = true
时,该过程可以正常完成。如果这不是一个可接受的解决方案,我发现使用 robocopy 的 /LOG:"C:\logs\robocopy.txt"
开关将其标准输出发送到外部日志文件也可以(尽管您失去了从进程中获取 file/directory 日志输出的能力对象本身)。