Service Hangs Up at Waitforexit After Calling Batch File

Service hangs up at WaitForExit after calling batch file

Here is what i use to execute batch files:

proc.StartInfo.FileName                 = target;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.UseShellExecute = false;

proc.Start();

proc.WaitForExit
(
(timeout <= 0)
? int.MaxValue : timeout * NO_MILLISECONDS_IN_A_SECOND *
NO_SECONDS_IN_A_MINUTE
);

errorMessage = proc.StandardError.ReadToEnd();
proc.WaitForExit();

outputMessage = proc.StandardOutput.ReadToEnd();
proc.WaitForExit();

I don't know if that will do the trick for you, but I don't have the problem of it hanging.

ProcessStartInfo hanging on WaitForExit ? Why?

The problem is that if you redirect StandardOutput and/or StandardError the internal buffer can become full. Whatever order you use, there can be a problem:

  • If you wait for the process to exit before reading StandardOutput the process can block trying to write to it, so the process never ends.
  • If you read from StandardOutput using ReadToEnd then your process can block if the process never closes StandardOutput (for example if it never terminates, or if it is blocked writing to StandardError).

The solution is to use asynchronous reads to ensure that the buffer doesn't get full. To avoid any deadlocks and collect up all output from both StandardOutput and StandardError you can do this:

EDIT: See answers below for how avoid an ObjectDisposedException if the timeout occurs.

using (Process process = new Process())
{
process.StartInfo.FileName = filename;
process.StartInfo.Arguments = arguments;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;

StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();

using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
{
process.OutputDataReceived += (sender, e) => {
if (e.Data == null)
{
outputWaitHandle.Set();
}
else
{
output.AppendLine(e.Data);
}
};
process.ErrorDataReceived += (sender, e) =>
{
if (e.Data == null)
{
errorWaitHandle.Set();
}
else
{
error.AppendLine(e.Data);
}
};

process.Start();

process.BeginOutputReadLine();
process.BeginErrorReadLine();

if (process.WaitForExit(timeout) &&
outputWaitHandle.WaitOne(timeout) &&
errorWaitHandle.WaitOne(timeout))
{
// Process completed. Check process.ExitCode here.
}
else
{
// Timed out.
}
}
}

Process.WaitForExit doesn't return even though Process.HasExited is true

This seems to be an artifact (I'd say "bug") in the specific implementation of the event-based asynchronous handling of StandardOutput and StandardError.

I noticed that while I was able to easily reproduce your problem, simply by running the code you provided (excellent code example, by the way! :) ), the process did not actually hang indefinitely. Rather, it returned from WaitForExit() once both of the child processes that had been started had themselves exited.

This seems to be an intentional part of the implementation of the Process class. In particular, in the Process.WaitForExit() method, once it has finished waiting on the process handle itself, it checks to see if a reader for either stdout or stderr has been created; if so, and if the timeout value for the WaitForExit() call is "infinite" (i.e. -1), the code actually waits for the end-of-stream on the reader(s).

Each respective reader is created only when the BeginOutputReadLine() or BeginErrorReadLine() method is called. The stdout and stderr streams are themselves not closed until the child processes have closed. So waiting on the end of those streams will block until that happens.

That WaitForExit() should behave differently depending on whether one has called either of the methods that start the event-based reading of the streams or not, and especially given that reading those streams directly does not cause WaitForExit() to behave that way, creates an inconsistency in the API that makes it much more difficult to understand and use. While I'd personally call this a bug, I suppose it's possible that the implementor(s) of the Process class are aware of this inconsistency and created it on purpose.

In any case, the work-around would be to read StandardOutput and StandardError directly instead of using the event-based part of the API. (Though of course, if one's code were to wait on those streams, one would see the same blocking behavior until the child processes close.)

For example (C#, because I don't know F# well enough to slap a code example like this together quickly :) ):

using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;

namespace TestSO26713374WaitForExit
{
class Program
{
static void Main(string[] args)
{
string foobat =
@"START ping -t localhost
START ping -t google.com
ECHO Batch file is done!
EXIT /B 123
";

File.WriteAllText("foo.bat", foobat);

Process p = new Process { StartInfo =
new ProcessStartInfo("foo.bat")
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
} };

p.Start();

var _ = ConsumeReader(p.StandardOutput);
_ = ConsumeReader(p.StandardError);

Console.WriteLine("Calling WaitForExit()...");
p.WaitForExit();
Console.WriteLine("Process has exited. Exit code: {0}", p.ExitCode);
Console.WriteLine("WaitForExit returned.");
}

async static Task ConsumeReader(TextReader reader)
{
string text;

while ((text = await reader.ReadLineAsync()) != null)
{
Console.WriteLine(text);
}
}
}
}

Hopefully the above work-around or something similar will address the basic issue you've run into. My thanks to commenter Niels Vorgaard Christensen for directing me to the problematic lines in the WaitForExit() method, so that I could improve this answer.

c# executing batch file still running after closing my app?

According to C# process hanging due to StandardOutput.ReadToEnd() and StandardError.ReadToEnd()
and Hanging process when run with .NET Process.Start -- what's wrong?

If output is kept filling with info, your thread might be blocked.

Following lines are blocking in your operation

    process.WaitForExit(TimeoutMin * 1000 * 60);
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

That is, process.Close is never called before batch is done.

If abortion is required, you need to close process somewhere else while application is exiting.

I reproduce your problem by

  1. Pasting code snippet to a button clicked event on a small winform application.
  2. Press the button.
  3. UI hung <--- That means code snippet is blocking a (UI) thread.

How to wait until my batch file is finished

This actually worked just fine for me:

System.Diagnostics.Process.Start("myBatFile.bat").WaitForExit();

As milton said, adding 'exit' at the end of your batch files is most likely a good idea.

Cheers



Related Topics



Leave a reply



Submit