Skip to content

Instantly share code, notes, and snippets.

@daxian-dbw
Last active October 31, 2016 21:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daxian-dbw/27ea3e2c9ad0878afb2734b5da68f5ae to your computer and use it in GitHub Desktop.
Save daxian-dbw/27ea3e2c9ad0878afb2734b5da68f5ae to your computer and use it in GitHub Desktop.
Redirect process output/error using event handler. This project experiments with BlockingCollection<T> and the encapsulation of the output/error data handler.
using System;
// A test process that write stdout and stderr.
namespace ConsoleApp {
public class Outter {
private static string[] outputs = new string[] { "Hello world",
"This is me",
"I'm here",
"You are awesome",
"Me too",
"Everyone is good at something" };
private static string[] errors = new string[] { "error one",
"error two",
"error three",
"error four",
"error five",
"error six" };
public static void Main(string[] args)
{
for (int i = 0; i < outputs.Length; i++)
{
Console.WriteLine(outputs[i]);
Console.Error.WriteLine(errors[i]);
}
}
}
}
using System;
using System.Diagnostics;
using System.Threading;
using System.Collections.Concurrent;
namespace ConsoleApplication
{
public class Program
{
private const string appPath = @"C:\tmp\outter.exe";
public static void Main(string[] args)
{
ProcessStartInfo outterStartInfo = new ProcessStartInfo(appPath) { RedirectStandardOutput = true, RedirectStandardError = true };
Process outter = new Process() { StartInfo = outterStartInfo };
Console.WriteLine("-- DEBUG -- Main thread: [" + Thread.CurrentThread.ManagedThreadId + "]");
Console.WriteLine("-- DEBUG -- StandardOutputEncoding: {0}",
outterStartInfo.StandardOutputEncoding == null
? "NULL" : outterStartInfo.StandardOutputEncoding.ToString());
Console.WriteLine("-- DEBUG -- StandardOutputEncoding: {0}\n",
outterStartInfo.StandardErrorEncoding == null
? "NULL" : outterStartInfo.StandardErrorEncoding.ToString());
var handler = new ProcessOutputHandler(true, true);
outter.OutputDataReceived += new DataReceivedEventHandler(handler.OutputHandler);
outter.ErrorDataReceived += new DataReceivedEventHandler(handler.ErrorHandler);
outter.Start();
outter.BeginOutputReadLine();
outter.BeginErrorReadLine();
Tuple<string, int> item = null;
while (true)
{
try
{
item = handler.BlockingQueue.Take();
}
catch (InvalidOperationException)
{
// The BlockingCollection is empty and the collection has been marked as complete for adding.
Console.WriteLine("~~~ Redirection is done ~~~");
break;
}
ConsoleColor origColor = Console.ForegroundColor;
try
{
if (item.Item2 == 2) { Console.ForegroundColor = ConsoleColor.Yellow; }
Console.WriteLine(item.Item1);
}
finally
{
if (item.Item2 == 2) { Console.ForegroundColor = origColor; }
}
}
outter.WaitForExit();
}
}
/// <summary>
/// Encapsulate the process output/error data handler
/// </summary>
public class ProcessOutputHandler
{
public ProcessOutputHandler (bool redirectOutput, bool redirectError)
{
Debug.Assert(redirectOutput || redirectError, "Caller should redirect at least one stream");
_blockingQueue = new BlockingCollection<Tuple<string, int>>(new ConcurrentQueue<Tuple<string, int>>());
if (redirectOutput) { _refCount ++; }
if (redirectError) { _refCount ++; }
}
private int _refCount = 0;
private BlockingCollection<Tuple<string, int>> _blockingQueue;
public BlockingCollection<Tuple<string, int>> BlockingQueue
{
get { return _blockingQueue; }
}
public void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
if (outLine.Data == null)
{
Console.WriteLine("-- DEBUG -- StandardOutput is closed.");
if (Interlocked.Decrement(ref _refCount) == 0)
{
Console.WriteLine("-- DEBUG -- CompleteAdding in Output Handler");
_blockingQueue.CompleteAdding();
}
}
else
{
_blockingQueue.Add(
Tuple.Create("OutputHandler thread: [" + Thread.CurrentThread.ManagedThreadId + "]", 1));
_blockingQueue.Add(
Tuple.Create("Output: " + outLine.Data, 1));
// Check if multiple output handler can be fired at the same time
Thread.Sleep(2000);
}
}
public void ErrorHandler(object sendingProcess, DataReceivedEventArgs errorLine)
{
if (errorLine.Data == null)
{
Console.WriteLine("\n-- DEBUG -- ErrorOutput is closed.");
if (Interlocked.Decrement(ref _refCount) == 0)
{
Console.WriteLine("-- DEBUG -- CompleteAdding in Error Handler");
_blockingQueue.CompleteAdding();
}
}
else
{
_blockingQueue.Add(
Tuple.Create("ErrorHandler thread: [" + Thread.CurrentThread.ManagedThreadId + "]", 2));
_blockingQueue.Add(
Tuple.Create("Error: " + errorLine.Data, 2));
// Check if multiple error handler can be fired at the same time
Thread.Sleep(5000);
}
}
}
}
// The handlers may be fired on different background threads, but for each kind of handler (output or error), only one instance
// gets fired at one time. So guarding `_blockingQueue.CompleteAdding();` with refCount decrement should be good.
/*
PS:29> .\event-redirect.exe
-- DEBUG -- Main thread: [1]
-- DEBUG -- StandardOutputEncoding: NULL
-- DEBUG -- StandardOutputEncoding: NULL
OutputHandler thread: [5]
ErrorHandler thread: [6]
Error: error one
Output: Hello world
OutputHandler thread: [4]
Output: This is me
OutputHandler thread: [4]
Output: I'm here
ErrorHandler thread: [6]
Error: error two
OutputHandler thread: [4]
Output: You are awesome
OutputHandler thread: [4]
Output: Me too
OutputHandler thread: [4]
ErrorHandler thread: [6]
Output: Everyone is good at something
Error: error three
-- DEBUG -- StandardOutput is closed.
ErrorHandler thread: [6]
Error: error four
ErrorHandler thread: [6]
Error: error five
ErrorHandler thread: [6]
Error: error six
-- DEBUG -- ErrorOutput is closed.
-- DEBUG -- CompleteAdding in Error Handler
~~~ Redirection is done ~~~
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment