问题
If process.Kill()
is called from another thread or even another program, the process never comes out of WaitForExit()
if the batch script used robocopy.exe until it is finished as if it wasn't killed.
Robocopy.exe is called from the batch script. Every other script or program ends as you'd expect.
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();
The batch script looks like:
@echo off
call "robocopy.exe" "somedir" "somedest" /mir /fp /ndl /njh /njs /ns
I have a feeling it has to do with the output handlers.
I tried using process.CancelErrorRead
and process.CancelOutputRead()
as well after the Kill()
call and before, no luck.
Oddly, if you use process.WaitForExit(timeout)
overload, it will return true immediately after Kill()
from the other thread. However, it's lying. The process is still running! If you try process.WaitForExit()
again, as per the MSDN doc, it will still wait for the process to finish despite HasExited
saying true.
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
回答1:
You are successfully killing the batch processor (cmd.exe) but doing so won't kill robocopy, which is a separate process.
It doesn't seem to be documented, but when we look at the .NET source code it turns out that the Process.WaitForExit()
method doesn't just wait for the process to exit, it also waits for end-of-file on the standard output and standard error streams. In this scenario, that means that it waits for robocopy to finish even after the batch processor has been killed.
(The overload of Process.WaitForExit
with a timeout does not have this extra logic.)
I think this constitutes a bug in the .NET framework. At the very least, it should be documented.
As a workaround, you can use .HasExited
and/or the version of WaitForExit
with a timeout to determine whether the process has exited or not. Of course, in your scenario you might prefer to wait for grandchild processes, in which case your code is already behaving as desired.
回答2:
I ran into the same problem. In my case, dropping the /mt
switch from the RoboCopy argument list seemed to fix the issue.
回答3:
Having followed up on Harry Johnston's helpful answer, I found that the process completes normally when you avoid RedirectStandardOutput = true
. If this isn't an acceptable solution I found that using robocopy's /LOG:"C:\logs\robocopy.txt"
switch to send its standard output to an external log file also works (although you lose the ability to get the file/directory log output from the process object itself).
回答4:
Looks like right now the only way to do this without the application knowing to terminate Robocopy.exe specifically is to do kill the children of the script process before killing the script itself:
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.
}
}
来源:https://stackoverflow.com/questions/44712364/batch-script-calling-robocopy-in-process-wont-terminate