Last active
October 31, 2016 21:59
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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