Standardoutput.Readtoend() Hangs

StandardOutput.ReadToEnd() hangs

Proposed solutions with BeginOutputReadLine() are a good way but in situations such as that, it is not applicable, because process (certainly with using WaitForExit()) exits earlier than async output finished completely.

So, I tried to implement it synchronously and found that the solution is in using Peek() method from StreamReader class. I added check for Peek() > -1 to sure that it is not the end of the stream as in MSDN article described and finally it works and stop hanging!

Here is the code:

var process = new Process();
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.WorkingDirectory = @"C:\test\";
process.StartInfo.FileName = "test.exe";
process.StartInfo.Arguments = "your arguments here";

process.Start();
var output = new List<string>();

while (process.StandardOutput.Peek() > -1)
{
output.Add(process.StandardOutput.ReadLine());
}

while (process.StandardError.Peek() > -1)
{
output.Add(process.StandardError.ReadLine());
}
process.WaitForExit();

C# process hanging due to StandardOutput.ReadToEnd() and StandardError.ReadToEnd()

As it turns out, the large quantities of output fill up the buffers for the ReadToEnd(), causing them to never finish. One solution that seemed to work reliably for me is to create an event handler to react to the output line by line without having to react to the large block of output/error at once.

//create event handler
process.OutputDataReceived += new DataReceivedEventHandler(
(s, e) =>
{
//do something with the output data 'e.Data'
log.Info("O: "+e.Data);
}
);
process.ErrorDataReceived += new DataReceivedEventHandler(
(s, e) =>
{
//do something with the error data 'e.Data'
log.Info("E: "+e.Data);
}
);
//start process
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();

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!

C# Process.StandardOutput.ReadLine() hangs

After few implemented tricks I've ended up with the following code. Quite tricky.

static async Task Main(string[] args)
{
using (SemaphoreSlim semaphore = new SemaphoreSlim(0, 1))
{
Process p = new Process()
{
StartInfo = new ProcessStartInfo("cmd")
{
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardError = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
Arguments = "/K set PROMPT=PROMPT$P$G$_",
}
};
p.ErrorDataReceived += (s, e) =>
{
if (e.Data?.Length > 0)
{
Console.WriteLine(e.Data);
}
};
bool wasprompt = false;
string prompt = "";
p.OutputDataReceived += (s, e) =>
{
if (e.Data?.Length > 0)
{
if (e.Data.StartsWith("PROMPT") && e.Data.EndsWith(">"))
{
prompt = e.Data.Substring(6, e.Data.Length - 7);
semaphore.Release();
wasprompt = true;
}
else
{
if (!wasprompt)
Console.WriteLine(e.Data);
else
wasprompt = false;
}
}
};
p.Start();
p.BeginErrorReadLine();
p.BeginOutputReadLine();
await semaphore.WaitAsync();
while (!p.HasExited)
{
Console.Write($"/SHELL/ {prompt}#> ");
string input = Console.ReadLine();
p.StandardInput.WriteLine(input);
if (input == "exit") break;
await semaphore.WaitAsync();
}
p.WaitForExit();
}

Console.WriteLine("Bye.");
Console.ReadKey();
}

Few times tested and looks like it works as you expect.

The idea was adding $_ to PROMPT environment variable to force the prompt printed to output. Then I catch that prompt and implement input/output synchronization with SemaphoreSlim.

process.standardoutput.ReadToEnd() always empty?

Why are you looping? Once it's read to the end, it's not going to be able to read any more data, is it?

Are you sure the text is actually being written to StandardOutput rather than StandardError?

(And yes, obviously you want to set RedirectStandardOutput to true rather than false. I assumed that was just a case of you copying the wrong version of your code.)

EDIT: As I've advised in the comments, you should read from standard output and standard error in separate threads. Do not wait until the process has exited - this can end up with a deadlock, where you're waiting for the process to exit, but the process is blocking trying to write to stderr/stdout because you haven't read from the buffer.

Alternatively you can subscribe to the OutputDataReceived and ErrorDataReceived events, to avoid using extra threads.

Started process hangs when redirected output is relatively big

The documentation for RedirectStandardOutput describes not one, but two possible deadlock scenarios (how's that for a useful and non-dangerous class library?!)

One occurs when both StandardOutput and StandardError are both redirected, and when the stream being read is full. This seems to perfectly describe your situation.

A pretty annoying situation, but read the full documentation and they describe how to avoid it occurring.

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

DeadLock Issues in Process.StandardOutput.ReadToEnd();

In short this is what may happen:

Application A (your code above) starts child process B and redirects standard output. Then A waits for the B process to exit. While A waits for B to exit, B produces output into the output stream (which A has redirected). This stream has a limited buffer size. If the buffer becomes full, it needs to be emptied in order to B to be able to continue writing into it. Since A is not reading until B has exited, you can end up in a situation where B will wait for the output buffer to be emptied, while A will wait for B to exit. Both are waiting for each other to take action, and you have a deadlock.

You can try the following code to demonstrate the problem:

ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "cmd";
psi.Arguments = @"/c dir C:\windows /s";
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;
Process p = Process.Start(psi);
p.WaitForExit();
string output = p.StandardOutput.ReadToEnd();

This will (moste likely) produce the situation where the output stream is full so that the child process (in this case "cmd") will wait for it to be cleared, while the code above will wait for cmd to finish.



Related Topics



Leave a reply



Submit