Reading from a child process's standard error

  • Thread starter Thread starter Nicola Musatti
  • Start date Start date
N

Nicola Musatti

Hallo,
Is there any way to perform a non blocking read from a child process's
standard error? I tried using the StandardError property of the
System.Diagnostics.Process class without success. According to the
documentation the StreamReader.Read(char[], int, int) function should not
block, but it does. Is there any alternative?

I'm using .NET 2.0 and VC# 2005.

Thank you.
Nicola Musatti
 
Nicola said:
Is there any way to perform a non blocking read from a child process's
standard error?

Yes. In fact, it's documented in the MSDN:
http://msdn.microsoft.com/library/system.diagnostics.process.standarderror(VS.80)

In short, use Process.BeginErrorReadLine().
I tried using the StandardError property of the
System.Diagnostics.Process class without success. According to the
documentation the StreamReader.Read(char[], int, int) function should not
block, but it does. Is there any alternative?
StreamReader.Read() *must* block. It's .BeginRead() that shouldn't (but may,
in exceptional circumstances).
 
First of all, not having anything useful to answer is not an excuse for being
offensive. If you read the Remarks sections in the VS80 documentation for the
StreamReader.Read(char[], int, int) and the StreamReader.ReadBlock() methods
it is only reasonable to come to the conclusion that this overload of Read()
is a non blocking version of ReadBlock(). One would wonder otherwise what the
purpose of ReadBlock could be.

By this I mean that it is reasonable to expect that Read(char[], int, int)
return immediately when there are no characters available to be read; instead
it blocks.

Here's a scaled down excerpt from my code:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;

namespace StreamReaderTest
{
class Program
{
static void Main(string[] args)
{
ProcessStartInfo psi = new ProcessStartInfo("cmd.exe");
psi.CreateNoWindow = true;
psi.ErrorDialog = false;
psi.RedirectStandardError = true;
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;

Process proc = new Process();
proc.StartInfo = psi;
proc.Start();
proc.StandardInput.WriteLine("dir");
string outLine = proc.StandardOutput.ReadLine();
char[] buffer = new char[4096];
proc.StandardError.Read(buffer, 0, 4096);
}
}
}

I need to replace the last two lines of code with a way to access the
program's standard error if there's any and not block if there isn't.

Cheers,
Nicola Musatti

Peter Duniho said:
Hallo,
Is there any way to perform a non blocking read from a child process's
standard error? I tried using the StandardError property of the
System.Diagnostics.Process class without success. According to the
documentation the StreamReader.Read(char[], int, int) function should not
block, but it does. Is there any alternative?

Please post a concise-but-complete code sample showing what you've tried,
and explaining what about it doesn't work as you expect.

You should be able to use the StandardError property to redirect stderr
from the process. But to do so successfully means reading the
documentation and understanding what you have to do. You need to set the
appropriate "Redirect..." property, and you need to understand that the
StreamReader.Read() method _always_ blocks until there is something to
read (so whatever you read that you _think_ says it should not block, you
misread).

If you post code to demonstrate what you're trying to do, it should be
possible to discuss and answer any issues you're having with that code.
Otherwise, not so much.

Pete
 
Jeroen Mostert said:
Yes. In fact, it's documented in the MSDN:
http://msdn.microsoft.com/library/system.diagnostics.process.standarderror(VS.80)

In short, use Process.BeginErrorReadLine().

Unfortunately the solutions described in the example above all assume that
the child process runs to termination without requiring further input. In my
case however I'm driving an interactive program and I need to decide what to
do based on its output and on its error. Now, while I know how much to read
from the child's standard output, I do not know if there's any error
available and how much.
I tried using the StandardError property of the
System.Diagnostics.Process class without success. According to the
documentation the StreamReader.Read(char[], int, int) function should not
block, but it does. Is there any alternative?
StreamReader.Read() *must* block. It's .BeginRead() that shouldn't (but may,
in exceptional circumstances).

I would call the BeginRead/EndRead pair asynchronous, but not non blocking.
That is, the read operation still blocks if there's nothing to read, but I
can defer when my program does block by choosing when to call EndRead(). In
my case however I have no other ways of knowing if there's anything to read
but check.

Cheers,
Nicola Musatti
 
Nicola said:
First of all, not having anything useful to answer is not an excuse for being
offensive. If you read the Remarks sections in the VS80 documentation for the
StreamReader.Read(char[], int, int) and the StreamReader.ReadBlock() methods
it is only reasonable to come to the conclusion that this overload of Read()
is a non blocking version of ReadBlock(). One would wonder otherwise what the
purpose of ReadBlock could be.

By this I mean that it is reasonable to expect that Read(char[], int, int)
return immediately when there are no characters available to be read; instead
it blocks.

The docs say:

"The number of characters that have been read, or 0 if at the end of the
stream and no data was read."

Always trust the docs over some fuzzy logic.

The read up to n at and block until at least 1 is read
is BTW a very common IO pattern in lots of languages
and technologies.

Arne
 
Peter Duniho said:
On Thu, 27 Nov 2008 07:18:01 -0800, Nicola Musatti
If you read the Remarks sections in the VS80 documentation for the
StreamReader.Read(char[], int, int) and the StreamReader.ReadBlock()
methods
it is only reasonable to come to the conclusion that this overload of
Read()
is a non blocking version of ReadBlock(). One would wonder otherwise
what the
purpose of ReadBlock could be.

The purpose of ReadBlock() is for the method to block until it's read a
many characters as you asked it to. Compare this to the Read() method
which blocks only until there are _any_ characters to read (it will then
read as many characters are available and return).

Both methods block. One blocks longer than the other.

I now realize that. I don't find it as useful as a real non-blocking
alternative would be, but there's little I can do about it.
By this I mean that it is reasonable to expect that Read(char[], int,
int)
return immediately when there are no characters available to be read;

It is often the case when someone misreads or misinterprets to then take
the position that the misreading or misinterpreting was "reasonable". So,
I doubt I can convince you that your misreadings/misinterpretings were not
in fact "reasonable".

Uhm, "reasonability" is in the mind of the beholder... I still maintain that
both my interpretation and my expectations are reasonable.
But the fact remains...
instead it blocks.

That's exactly what it's supposed to do. If you want non-blocking
semantics, you need to use the asynchronous methods.
[...]
Process proc = new Process();
proc.StartInfo = psi;
proc.Start();
proc.StandardInput.WriteLine("dir");
string outLine = proc.StandardOutput.ReadLine();
char[] buffer = new char[4096];
proc.StandardError.Read(buffer, 0, 4096);
}
}
}

I need to replace the last two lines of code with a way to access the
program's standard error if there's any and not block if there isn't.

You can't. And it's a mistake to try to read both StandardOutput and
StandardError from the same thread. See the Process documentation for
details, but the basic problem is that if you get stuck waiting on one,
the buffer for the other may fill, then blocking the process you're trying
to read from, thus creating a deadlock condition.

I'm fully aware of the problem and I realize that without a form of
non-blocking I/O the alternatives you outline below are the only available
ones. It's a pity, though, because both lead to much more complicated code
than non-blocking I/O would.
You should use the asynchronous methods or put blocking read code in a
different thread, at the very least for one of the streams, so that you
can ensure that both streams can always be read.

Unfortunately both approaches aren't very convenient for what I need to do;
I have a loop where I
- Issue a command;
- Read output until a conventional line;
- If there's an error condition, read whatever error message is available.

With non blocking I/O it only takes a call to Read(), without it I need to
read error lines from a different thread and put them in a buffer, taking
care of synchronization between the two threads.
And quit being so trigger-happy about taking offense to factual
statements. There's no need for that kind of sensitivity, and it only
makes people not want to help you.

I realize that I'm new to this forum and I can't expect the respect I gained
in different ones to carry over automatically. Still I find it irksome when
others appear to assume that I don't do my homework. But I'm ready to
consider the incident closed if you are too.

Cheers,
Nicola Musatti
 
Arne Vajhøj said:
Nicola said:
First of all, not having anything useful to answer is not an excuse for being
offensive. If you read the Remarks sections in the VS80 documentation for the
StreamReader.Read(char[], int, int) and the StreamReader.ReadBlock() methods
it is only reasonable to come to the conclusion that this overload of Read()
is a non blocking version of ReadBlock(). One would wonder otherwise what the
purpose of ReadBlock could be.

By this I mean that it is reasonable to expect that Read(char[], int, int)
return immediately when there are no characters available to be read; instead
it blocks.

The docs say:

"The number of characters that have been read, or 0 if at the end of the
stream and no data was read."

Always trust the docs over some fuzzy logic.

I realize I misread "at the end of the stream" to mean "none is available".
The read up to n at and block until at least 1 is read
is BTW a very common IO pattern in lots of languages
and technologies.

I have no reason to disbelieve you, but I do find non blocking I/O an
essential complement.

Cheers,
Nicola Musatti
 
Peter Duniho said:
On Thu, 27 Nov 2008 07:18:01 -0800, Nicola Musatti
Process proc = new Process();
proc.StartInfo = psi;
proc.Start();
proc.StandardInput.WriteLine("dir");
string outLine = proc.StandardOutput.ReadLine();
char[] buffer = new char[4096];
proc.StandardError.Read(buffer, 0, 4096);
}
}
}

I need to replace the last two lines of code with a way to access the
program's standard error if there's any and not block if there isn't.

You can't. And it's a mistake to try to read both StandardOutput and
StandardError from the same thread. See the Process documentation for
details, but the basic problem is that if you get stuck waiting on one,
the buffer for the other may fill, then blocking the process you're trying
to read from, thus creating a deadlock condition.

You should use the asynchronous methods or put blocking read code in a
different thread, at the very least for one of the streams, so that you
can ensure that both streams can always be read.

For those interested the following modified example does use asynchronous
I/O to achieve what I wanted:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;

namespace StreamReaderTest
{
class Program
{
private static string buffer = null;

private static void ErrorDataHandler(object sendingProcess,
DataReceivedEventArgs errLine)
{
if ( buffer == null && !String.IsNullOrEmpty(errLine.Data) )
buffer = errLine.Data;
}

static void Main(string[] args)
{
ProcessStartInfo psi = new ProcessStartInfo("cmd.exe");
psi.CreateNoWindow = true;
psi.ErrorDialog = false;
psi.RedirectStandardError = true;
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;

Process proc = new Process();
proc.StartInfo = psi;
proc.ErrorDataReceived +=
new DataReceivedEventHandler(ErrorDataHandler);

proc.Start();
proc.BeginErrorReadLine();
proc.StandardInput.WriteLine("foo");
string outLine = proc.StandardOutput.ReadLine();
}
}
}

Cheers,
Nicola Musatti
 
Back
Top