Processstartinfo Hanging on "Waitforexit" - Why

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 sometimes hangs while waiting for Exit

Let's start with a recap of the accepted answer in a related post.

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).

Even the accepted answer, however, struggles with the order of execution in certain cases.

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

It's in these kind of situations, where you want to orchestrate several events, that Rx really shines.

Note the .NET implementation of Rx is available as the System.Reactive NuGet package.

Let's dive in to see how Rx facilitates working with events.

// Subscribe to OutputData
Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.OutputDataReceived))
.Subscribe(
eventPattern => output.AppendLine(eventPattern.EventArgs.Data),
exception => error.AppendLine(exception.Message)
).DisposeWith(disposables);

FromEventPattern allows us to map distinct occurrences of an event to a unified stream (aka observable). This allows us to handle the events in a pipeline (with LINQ-like semantics). The Subscribe overload used here is provided with an Action<EventPattern<...>> and an Action<Exception>. Whenever the observed event is raised, its sender and args will be wrapped by EventPattern and pushed through the Action<EventPattern<...>>. When an exception is raised in the pipeline, Action<Exception> is used.

One of the drawbacks of the Event pattern, clearly illustrated in this use case (and by all the workarounds in the referenced post), is that it's not apparent when / where to unsubscribe the event handlers.

With Rx we get back an IDisposable when we make a subscription. When we dispose of it, we effectively end the subscription. With the addition of the DisposeWith extension method (borrowed from RxUI), we can add multiple IDisposables to a CompositeDisposable (named disposables in the code samples). When we're all done, we can end all subscriptions with one call to disposables.Dispose().

To be sure, there's nothing we can do with Rx, that we wouldn't be able to do with vanilla .NET. The resulting code is just a lot easier to reason about, once you've adapted to the functional way of thinking.

public static void ExecuteScriptRx(string path, int processTimeOutMilliseconds, out string logs, out bool success, params string[] args)
{
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();

using (var process = new Process())
using (var disposables = new CompositeDisposable())
{
process.StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "powershell.exe",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
Arguments = $"-ExecutionPolicy Bypass -File \"{path}\"",
WorkingDirectory = Path.GetDirectoryName(path)
};

if (args.Length > 0)
{
var arguments = string.Join(" ", args.Select(x => $"\"{x}\""));
process.StartInfo.Arguments += $" {arguments}";
}

output.AppendLine($"args:'{process.StartInfo.Arguments}'");

// Raise the Process.Exited event when the process terminates.
process.EnableRaisingEvents = true;

// Subscribe to OutputData
Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.OutputDataReceived))
.Subscribe(
eventPattern => output.AppendLine(eventPattern.EventArgs.Data),
exception => error.AppendLine(exception.Message)
).DisposeWith(disposables);

// Subscribe to ErrorData
Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.ErrorDataReceived))
.Subscribe(
eventPattern => error.AppendLine(eventPattern.EventArgs.Data),
exception => error.AppendLine(exception.Message)
).DisposeWith(disposables);

var processExited =
// Observable will tick when the process has gracefully exited.
Observable.FromEventPattern<EventArgs>(process, nameof(Process.Exited))
// First two lines to tick true when the process has gracefully exited and false when it has timed out.
.Select(_ => true)
.Timeout(TimeSpan.FromMilliseconds(processTimeOutMilliseconds), Observable.Return(false))
// Force termination when the process timed out
.Do(exitedSuccessfully => { if (!exitedSuccessfully) { try { process.Kill(); } catch {} } } );

// Subscribe to the Process.Exited event.
processExited
.Subscribe()
.DisposeWith(disposables);

// Start process(ing)
process.Start();

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

// Wait for the process to terminate (gracefully or forced)
processExited.Take(1).Wait();

logs = output + Environment.NewLine + error;
success = process.ExitCode == 0;
}
}

We already discussed the first part, where we map our events to observables, so we can jump straight to the meaty part. Here we assign our observable to the processExited variable, because we want to use it more than once.

First, when we activate it, by calling Subscribe. And later on when we want to 'await' its first value.

var processExited =
// Observable will tick when the process has gracefully exited.
Observable.FromEventPattern<EventArgs>(process, nameof(Process.Exited))
// First two lines to tick true when the process has gracefully exited and false when it has timed out.
.Select(_ => true)
.Timeout(TimeSpan.FromMilliseconds(processTimeOutMilliseconds), Observable.Return(false))
// Force termination when the process timed out
.Do(exitedSuccessfully => { if (!exitedSuccessfully) { try { process.Kill(); } catch {} } } );

// Subscribe to the Process.Exited event.
processExited
.Subscribe()
.DisposeWith(disposables);

// Start process(ing)
...

// Wait for the process to terminate (gracefully or forced)
processExited.Take(1).Wait();

One of the problems with OP is that it assumes process.WaitForExit(processTimeOutMiliseconds) will terminate the process when it times out. From MSDN:

Instructs the Process component to wait the specified number of milliseconds for the associated process to exit.

Instead, when it times out, it merely returns control to the current thread (i.e. it stops blocking). You need to manually force termination when the process times out. To know when time out has occurred, we can map the Process.Exited event to a processExited observable for processing. This way we can prepare the input for the Do operator.

The code is pretty self-explanatory. If exitedSuccessfully the process will have terminated gracefully. If not exitedSuccessfully, termination will need to be forced. Note that process.Kill() is executed asynchronously, ref remarks. However, calling process.WaitForExit() right after will open up the possibility for deadlocks again. So even in the case of forced termination, it's better to let all disposables be cleaned up when the using scope ends, as the output can be considered interrupted / corrupted anyway.

The try catch construct is reserved for the exceptional case (no pun intended) where you've aligned processTimeOutMilliseconds with the actual time needed by the process to complete. In other words, a race condition occurs between the Process.Exited event and the timer. The possibility of this happening is again magnified by the asynchronous nature of process.Kill(). I've encountered it once during testing.


For completeness, the DisposeWith extension method.

/// <summary>
/// Extension methods associated with the IDisposable interface.
/// </summary>
public static class DisposableExtensions
{
/// <summary>
/// Ensures the provided disposable is disposed with the specified <see cref="CompositeDisposable"/>.
/// </summary>
public static T DisposeWith<T>(this T item, CompositeDisposable compositeDisposable)
where T : IDisposable
{
if (compositeDisposable == null)
{
throw new ArgumentNullException(nameof(compositeDisposable));
}

compositeDisposable.Add(item);
return item;
}
}

Process.WaitForExit hangs - but I only redirect standard input?

You may refer to RedirectStandardInput documentation and example. You should close the StandardInput stream after writing a line to it to properly handle a pause statement

p.StandardInput.WriteLine();
p.StandardInput.Close();
p.WaitForExit();

ProcessStartInfo WaitForExit Timeout

After a lot of trying, testing and searching i am sure my code works.
So i still don´t really know why my code stoped working.
But changing from Adobe Reader 9.0 on the server to 7.0 it now works.

When i was debuging locally with Adobe Reader 9.0 it also worked, so i think maybe there was an update on the webserver. I diden`t verifyed that yet.

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.

StandardOutput.ReadToEnd Hangs Indefinitely

It turned out to be a script permission issue for my machine. Once I changed it to ByPass it worked. So, it wasn't the code!

Hanging when process WaitForExit in for loop

WaitForExit, unsurprisingly, will block the thread until the process has exited.

Alternatively, you can use the Exited event to get notified when the process has exited.

You will also need to set the EnableRaisingEvents property to true in order for the Exited event to be fired.

myProcess.EnabledRaisingEvents = true;
myProcess.Exited += new EventHandler(myProcess_Exited);
myProcess.Start();

void myProcess_Exited(object sender, EventArgs e)
{
//process has exited, implement logic here
listBox1.Items.Remove...
}


Related Topics



Leave a reply



Submit