4

See the following class below, I use it to read the output from any of the processes that are added to it. At the moment I'm starting up a Jar that acts as a channel adapter between the C# application and a JMS messaging broker. The only problem is, that when reading from the StandardOutput of the process the thread blocks on the reader.Peek() call.

After some debugging I found out that this only happens if no output has been written to the StandardOutput stream of the process. I've played around a bit with the immediate window to figure out if I can find a way to determine whether the underlying stream is empty. But so far all calls to properties like Stream.CanRead or FileStream.Length throw InvalidOperationExceptions or return output that cannot be used to check for this condition.

Furthermore, I also tried using the OutputDataReceived and ErrorDataReceived events, but for some reason they never seem to fire.

So my question is; is there any way that can be used to cleanly read the output from the processes as it becomes available?

The class in which the output is read:

namespace ReparatieSysteem.Lib
{
    internal delegate void NewOutputLineEvent(Process process, int port, string line);

    class ProcessListener
    {
        private static readonly Dictionary<int, Process> processes;
        private static readonly Thread thread;

        static ProcessListener()
        {
            processes = new Dictionary<int, Process>();
            thread = new Thread(RunThread);
            thread.Start();
        }

        private static void PollProcesses()
        {
            foreach (var item in processes.Where(p => p.Value.HasExited).ToList())
            {
                processes.Remove(item.Key);
            }

            foreach (var item in processes)
            {
                SendLines(item.Value, item.Key, ReadReader(item.Value.StandardError));
                SendLines(item.Value, item.Key, ReadReader(item.Value.StandardOutput));
            }

        }

        private static void SendLines(Process process, int port, IEnumerable<String> lines)
        {
            foreach(var line in lines)
            {
                if (OnNewOutput != null)
                {
                    OnNewOutput(process, port, line);
                }
            }
        }

        private static IEnumerable<string> ReadReader(StreamReader reader)
        {
            while (reader.Peek() >= 0)
            {
                yield return reader.ReadLine();
            }
        }

        private static void RunThread()
        {
            while(true)
            {
                if (processes.Count > 0)
                {
                    PollProcesses();
                }

                Thread.Sleep(200);
            }
        }

        public static void AddProcess(int port, Process process)
        {
            processes.Add(port, process);
        }

        public static event NewOutputLineEvent OnNewOutput;
    }
}

The code that creates the process:

var procStartInfo = new ProcessStartInfo("java", 
            string.Format("-jar {0} {1} {2} {3}", JarPath, ConnectionName, _listenQueue, _listenPort))
        {
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true
        };

        _brokerProcess = new Process { StartInfo = procStartInfo };

        _brokerProcess.Start();

        ShutdownListener.OnApplicationQuit += _brokerProcess.Kill;

        ProcessListener.AddProcess(_listenPort, _brokerProcess);
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
TheDutchDevil
  • 826
  • 11
  • 24
  • In this case I programmed the underlying jar, so I know exactly what it outputs (A few lines when it starts, then a line when the C# application connects over a socket). The problem just is that for some reason Peek blocks when it is accessed before output has been written, and that the events output nothing, for some strange reason. As for using ReadAsync, does that require the use of async etc? And if so, are there any examples available online. Since I'm not that familiar with the async keyword in C#. – TheDutchDevil Jun 16 '14 at 21:54
  • Okay, it worked! Although eventually I ended up using this sample: http://msdn.microsoft.com/en-us/library/vstudio/system.io.streamreader.readlineasync because the FileStream.Length property cannot be called, because Process output streams are not seekable. So if you could please post that as an answer I can mark it as the accapted answer. – TheDutchDevil Jun 16 '14 at 22:27

1 Answers1

2

Seems that this is a known bug in StreamReader implementation, as suggested in this answer. The workaround in your case is to use asynchronous stream methods like StreamReader.ReadAsync or StreamReader.ReadLineAsync.

Community
  • 1
  • 1
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
  • I didn't even know there was a bug! Thanks to you, I instead resolved the issue by first using ReadLine(), then checking if it was null. If yes, then it breaks the loop. Otherwise, it gets the data that was read from ReadLine() to do other stuff. – Kaitlyn Oct 17 '15 at 13:11