JAL Computing

 

Call a Console Application From a Multi-Threaded C# Application

In this article, you will learn about:

bulletSystem.Diagnostic.Process
bulletImplementing Threaded I/O
bulletUsing Delegates and Events
bulletCallbacks
bulletSTA WinForms Programming
bulletCalling a WinForms Program from a Worker Thread
bulletUsing lock to Protect a Critical Section of Code

You can add a graphical user interface to a command line "Console Application" using Windows Forms Programming and the System.Diagnostic.Process class like this:

Unfortunately, this turns out to be a non-trivial project. My goal was to write a generic Windows Form application that could call a console application using System.Diagnostic.Process, passing in args and cin and reading cout and cerr. To avoid blocking I/O I had to use threads to write to cin and read cout and cerr. Unfortunately, when the console application exits, the threads are still reading cout and cerr, truncating the output. My solution is to first call myProcess.WaitForExit(waitForExitTime). Once the console application exits or times out, I call a private function WaitForThreadExit(waitForThreadTime). This private function waits until all three threads have completed or timed out.

When the client tells myThreadedProcess to start, the client would block until the console application is closed. This is thread safe, but makes the GUI unresponsive. To increase the responsiveness of the caller, I decided to launch the console application in an asynchronous worker thread. The client must pass an event of type ThreadedProcessEventHandler to the StartInThread method. The StartInThread method will use this event to notify the caller when the worker thread is done. The StartInThread method should only be invoked once.

The delegate looks like this: 

public delegate void ThreadedProcessEventHandler(object worker, ThreadedProcessEventArgs e); 

If the callback is to a Windows Form application, then the event handler method must call BeginInvoke to marshal the call to the main WinForms thread. If the caller is a free threaded application (not WinForms) the event handler method must be thread safe. 

A Matter of Threads

According to MSDN: "Windows Forms uses the single-threaded apartment (STA) model because Windows Forms is based on native Win32 windows that are inherently apartment-threaded. The STA model implies that a window can be created on any thread, but it cannot switch threads once created, and all function calls to it must occur on its creation thread. Outside Windows Forms, classes in the .NET Framework use the free threading model."

However, Willy Denoyette [MVP] argues that "Windows forms are not STA threaded, Windows forms have thread affinity. That means the handles (HWND) of windows controls should only be used by code running on the thread that created the control (the window handle). Whenever you need to access a control from another thread, you need to marshal the call (using Control.Invoke or BeginInvoke) to make sure the code runs on the UI thread that owns the HWND of the control.

The STA threading requirement is a COM only requirement, because a few controls in the control suite are effectively ActiveX control types and because all ActiveX controls are STA threaded, you have to set the attributeto STA Thread on your Main function to make sure your UI thread enters a STA."

I reckon that the safe statement is to simply say that WinForms is not free threaded.

Here are some links that may be helpful:

Multi-threaded Windows Form Example
Processes, Threads, and Apartments

A C# Console Example

First, here is a simple non threaded C# project that calls the command line executable IPCONFIG.exe with the argument "/all". It redirects cout and stores the result in a string. Alternatively, you can redirect cout to a StreamReader and read input line by line. Underneath, the magic is done with un-named pipes. This non-threaded approach may be best if you do not need access to cin and cout, or read both cout and cerr.

using System;
using System.Diagnostics;
using System.IO;
namespace TestProcess
{
    /// <summary>
    /// Summary description for Class1.
    /// </summary>
    class Class1
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            //// TODO: Add code to start application here//
            Process myProcess = new Process();

            ProcessStartInfo myProcessStartInfo = 
new ProcessStartInfo("IPCONFIG.exe" );
            myProcessStartInfo.UseShellExecute = false;
            myProcessStartInfo.RedirectStandardOutput = true;
            myProcessStartInfo.Arguments= "/all";

            myProcess.StartInfo = myProcessStartInfo;
            myProcess.Start();

            //StreamReader myStreamReader = myProcess.StandardOutput;
            //string output = myStreamReader.ReadLine();

            string output = myProcess.StandardOutput.ReadToEnd();

            // block until exe exits
            myProcess.WaitForExit();
            Console.WriteLine(output);

	    // release resources
            myProcess.Close();
            Console.ReadLine();
        }
    }
}

Pretty cool!

If you want to redirect cerr, cout and cin, you should use separate threads as discussed in this article

Note: You can only call executables (.exe) if you set UseShellExecute to false. To run a dll when UseShellExecute is false, you can use "rundll32" to do this as in:

	Process myProcess = new Process();
	ProcessStartInfo myProcessStartInfo = 
		new ProcessStartInfo("rundll32.exe" );
	myProcessStartInfo.UseShellExecute = false;
	myProcessStartInfo.RedirectStandardOutput = true;
	myProcessStartInfo.Arguments= "url.dll ,FileProtocolHandler www.microsoft.com";
	myProcess.StartInfo = myProcessStartInfo;
	myProcess.Start();

Warning: If you try to start the process "http://www.microsoft.com" from a [MTAThreaded] application with UseShellExecute set to true, your [MTAThreaded] application will throw an exception.  Calls to an out of process COM server are adapted to the callers threading model.

C++ ConsoleServer

Before diving into writing a multi-threaded application that redirects cerr, cout and cin, create a C++ Win32 console application called ConsoleServer. In Visual Studio 2003 do this as: File --> New --> Project... and choose Win32 --> Win32 Console. Name the project ConsoleServer. Add #include <string> to the stdafx.h header file as:

// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#pragma once

#include <iostream>
#include <tchar.h>

// TODO: reference additional headers your program requires here
#include <string>

Add using namespace std; and the following code to _tmain in the ConsoleServer.cpp file as:

// ConsoleServer.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
using namespace std
int
_tmain(int argc, _TCHAR* argv[])
{

    // TODO: Please replace the sample code below with your own.

    string inString;

    while
(getline(cin,inString)) 
    {

        if
(inString == "q") 
        {
            break;
        }
        cout << inString << endl;
        cerr << inString << endl;
    }
   
return 0;
}

Note: cout is buffered so you need to flush the buffer with endl or flush.

Compile the project and copy the ConsoleServer.exe to the bin/Debug folder of the multi-threaded C# application that follows. You can also download the project here.

Using Delegates and Events

In the ThreadedProcess application that follows, I used delegates and events to notify the client that the contained process has closed. Using delegates and events is not very straight forward, so I will walk you through this. 

Thanks to Bruno Jouhier [MVP] and Jason Black [MSFT] for their help in implementing a callback to a Windows Form application from a worker thread.

A delegate defines a Type with a given signature. When you declare a delegate the compiler auto magically generates a class definition that inherits from System.MulticastDelegate with a constructor that that takes an instance target and method pointer as arguments. If the target is null, then the method is static. The class provides methods Invoke, BeginInvoke and EndInvoke that match the delegate prototype. In other words, the class encapsulates a type safe function pointer to "some" method. Here is the declaration of a delegate that takes two arguments. This declaration is part of the ThreadedProcessClass namespace.

public delegate void ThreadedProcessEventHandler(object worker, ThreadedProcessEventArgs args); 

The prototype takes a reference to the sender and a ThreadedProcessEventArgs object as an argument. args is an immutable object and hence inherently thread safe. This "Args" class is used to return values to the client. It is declared as:

// immutable class used as return value in thread
public
class ThreadedProcessEventArgs : EventArgs
{

    private
string consoleOutput;
    private
string consoleError;
    private
int exitCode;
    // constructor
    public
ThreadedProcessEventArgs(string consoleOutput,string consoleError,int exitCode)
    {

        this
.consoleOutput= consoleOutput;
        this
.consoleError= consoleError;
        this
.exitCode= exitCode;
    }

    // properties
    public string ConsoleOutput{ get {return consoleOutput;}}
    public
string ConsoleError { get {return consoleError;}}
    public
int ExitCode {get {return exitCode;}}
 }

In the client Windows Form project, declare an event as:

// declare event handler of type ThreadedProcessEventHandler defined in ThreadedProcess class
public event ThreadedProcess.ThreadedProcessEventHandler threadedProcessEventDispatcher;

This declares an event variable threadedProcessEventDispatcher of type ThreadedProcessEventHandler. The event "expands" the ThreadedProcessEventHandler type to include "add" and "remove" methods for registering listeners or subscribers to the ThreadedProcessEventHandler. The event keyword also adds a static method that maps all ThreadedProcessEventHandler calls to the threadedProcessEventDispatcher. The threadedProcessEventDispatcher is used to register methods that are interested in handling an event of type ThreadedProcessEventHandler using the += notation.

The event keyword is analogous to a property for an instance field. Just as you can add get and set semantics to an instance field, you can add Add, Remove and Call semantics to a delegate using the event key word.

In this project only a single method, Form1_OnThreadedProcessEvent, registers an interest in a ThreadedProcessEventHandler event. Here is the [STAThread] aware method Form1_OnThreadedProcessEvent:

// WinForms is based on native Win32 STA code so that worker thread cannot
// directly invoke methods in the main thread on callback
// If not called from main WinForms thread, must use windows messaging
// to marshal call to main WinForms thread. For each callback this method
// is invoked twice, once from worker thread and then once from main thread
public void Form1_OnThreadedProcessEvent(object worker, ThreadedProcessEventArgs e)
{

    if
(textBoxOut.InvokeRequired)
    {

        this
.BeginInvoke(new ThreadedProcess.ThreadedProcessEventHandler(Form1_OnThreadedProcessEvent),
                                new
object[]{worker,e});
    }
    else
   
// back in main thread
    {
        textBoxOut.Text= e.ConsoleOutput;
        textBoxError.Text= e.ConsoleError;
        textBoxExitCode.Text= e.ExitCode.ToString();
    }
}

In the forms constructor, register this Form1_OnThreadedProcessEvent method with the threadedProcessEventDispatcher:

public Form1()
{

    //
    // Required for Windows Form Designer support
    //

    InitializeComponent();

    // TODO: Add any constructor code after InitializeComponent call
    //
    // add listener to receive ThreadedProcessEventHandler from ThreadedProcess.StartInThread
    // eventually handle event in Form1_OnThreadedProcessEvent method

    threadedProcessEventDispatcher +=
new ThreadedProcess.ThreadedProcessEventHandler(Form1_OnThreadedProcessEvent);
}

Note: The constructor for the auto generated class ThreadedProcessEventHandler takes two arguments, a target and a method pointer. In the above line only one argument is passed to the ThreadedProcessEventHandler! This works only because the C# compiler generates the correct call to the constructor for you.

In the private void buttonSubmit_Click(object sender, System.EventArgs e) method, pass the threadedProcessEventDispatcher of type ThreadedProcessEventHandler to the StartInThread method:

ThreadedProcess process = new ThreadedProcess();
process.Path= pathToExe;
process.Args= arguments;
process.WaitForExitTime= waitForExitTime;
process.WaitForThreadTime= waitForThreadTime;
process.ConsoleInput= consoleInput;
process.StartInThread(threadedProcessEventDispatcher);

When the worker thread is done it fires off the event, notifying the client that it is done as in:

public void StartInThread(ThreadedProcessEventHandler callhome){
    if (callhome == null) {throw new ArgumentNullException();}
    ...
    
callhome(this,new ThreadedProcessEventArgs(this.ConsoleOutput,this.ConsoleError,this.ExitCode));
}

Admittedly this is an unusual way to write a method call! It is up to the compiler to recognize that callhome is a delegate and generate a call to the target instance using the method pointer by calling Invoke.

Note: As best as I can tell the compiler does something like this behind the scenes:

Delegate[] handlers= callhome.GetInvocationList();
foreach (Delegate d in handlers)
{
d.DynamicInvoke(
new object[]{this,
   
new ThreadedProcessEventArgs(this.ConsoleOutput,this.ConsoleError,this.ExitCode)});
}

So you pass a variable threadedProcessEventDispatcher that refers to an "expanded" ThreadedProcessEventHandler. When the worker thread is done, it fires an event of type ThreadedProcessEventHandler which is intercepted by the threadedProcessEventDispatcher. The event is then dispatched to the only registered listener, Form1_OnThreadedProcessEvent. All of this is done in a type safe manner using a "type safe function pointer" or delegate.

More about delegates here.

Using Delegates Alone

I must admit that the concept of using delegates _and_ events seems like a bit of overkill. It seems to me that it is much simpler just to declare the delegate, pass the Form1_OnThreadedEvent to the delegate constructor and pass the instance of the delegate to StartInThread. Besides, that would have saved me a lot of explaining!

public ThreadedProcess.ThreadedProcessEventHandler testIt;
testIt= new ThreadedProcess.ThreadedProcessEventHandler(Form1_OnThreadedProcessEvent);
process.StartInThread(testIt);

Using Threads

In this project, three I/O threads (cin_thread, cerr_thread, and cout_thread) are launched to write and read asynchronously to the console application. First, three I/O methods are created. Each of these methods will be launched in a separate background thread. Here is the cout_thread method:

private void cout_thread()
{

    try
    {
        // if process does not exit normally, will
        // throw exception on close

        while
(!myProcess.HasExited)
        {
            your_cout.WriteLine(my_cout.ReadLine());
        }
        your_cout.WriteLine(my_cout.ReadToEnd());
    }

    catch
(Exception e) {}
    finally

     {
            notifyCoutDone();
    }
}

The cout_thread method loops until the console application exits and then reads to the end of the buffer. If the worker thread times out, the console application will be "forced" to quit. This results in an exception being thrown in the try clause, causing the cout_thread method to exit! In other words, there is no reason to call abort on this thread if the process fails to exit in a timely manner. No matter what, the notifyCoutDone() method will be called. The notifyCoutDone() method is used to flag the successful exit of the cout_thread.

To start the cout_thread in a separate thread, create a new Thread, set the IsBackground property to true and then call Start().

// launch thread threads in background for asynchronous I/O
thread_cout= new Thread(new ThreadStart(this.cout_thread));
thread_cout.IsBackground=
true;
thread_cout.Start();

Finally, launch the ThreadedProcess.Start_thread() method in a worker thread to avoid "blocking" the GUI. Here is the helper class that launches the worker thread.

//ASSERT only call this method once per instance
//ASSERT callhome is not null

public
void StartInThread(ThreadedProcessEventHandler callhome)
{
    if (callhome == null) {throw new ArgumentNullException();}
    // block here to avoid concurrency conflict
    // eg two threads trying to call StartInThread simultaneously
    lock(this)
    {
        if (isStarted) {throw new Exception("Thread is running.");}
        this
.isStarted= true;
    }

    this
.callhome= callhome;
    // launch console app in new thread
    Thread thread_start= new Thread(new ThreadStart(Start_thread));
    thread_start.IsBackground=
true;
    thread_start.Start();
}

The only unusual code is the call to lock. lock essentially wraps the "critical" section of code in try/finally, Monitor.Enter() and Monitor.Exit(). Even if two threads try to enter this section of code almost "simultaneously", only one thread can execute the "locked" section of code at a time. In essence, threads that try to call this section of code must queue up to enter the "critical" code section. Without a lock, it is possible for two threads to "simultaneously" enter the "critical" section of code. Both threads could read the value of isStarted as false before either thread can set the isStarted flag to true. This would defeat the purpose of the flag, allowing two threads to start a console application. Each thread would be trying to read and write to a single set of instance variables. Not a very pretty sight to contemplate! 

The lock code expands to:

Monitor.Enter(this);
try
{
    if (isStarted) {throw new Exception("Thread is running.");}
    this.isStarted= true;
}
finally
{
    Monitor.Exit(
this);
}

Calls to this section of code block on the call to Monitor.Enter(this). Only one thread can obtain a lock on this. Any other thread that wishes to enter this section of code must wait until the active thread releases the lock on this. Once the code is executed, the lock on this is released. The use of finally insures that the lock is released before the thread exits the code block, even if an exception is thrown in the try clause.

The WaitForThread Method

The remainder of the ThreadedProcess class code is pretty self explanatory except perhaps the WaitForThreadExit method. The WaitForThreadExit method runs in a loop checking to see if the three I/O threads have exited. If the threads have exited the loops breaks. If the waitForThreadTime is exceeded the also method exits. In order to improve the responsiveness of this method, I decided to run the loop every waitForThreadTime/10 milliseconds. Typically the I/O threads have exited 100 milliseconds after the process exits. If the process does not exit, then the method blocks for the full waitForThreadTime.

private void WaitForThreadExit(int waitForThreadTime)
{

    if
(!isStarted || isClosed) {throw new Exception("Internal logic error.") ;}

    int
elapsedTime=0;
    // exit if all I/O threads have returned or when times out
    while (elapsedTime <= waitForThreadTime)
    {
        elapsedTime+= waitForThreadTime/10;
        Thread.Sleep(waitForThreadTime/10);
        if (isCinDone && isCerrDone && isCoutDone){break;}
    }
    System.Console.WriteLine(elapsedTime);
}

There is really no reason to break this code out into a separate method except for code reading clarity. This method only works appropriately if it is called in a worker thread that can sleep and block without interfering with the user interface. If this method was invoked by a GUI thread, consider using a timer.

Wrap Console Project

Here is the generic GUI application in action.

ThreadedProcess Project

Here is the source code for the ThreadedProcessEventArgs and ThreadedProcess classes. You can download the fully functioning project from a link at the end of this article.

	// *** THREADED_PROCESS_EVENT_ARGS CLASS ***
	//
	// immutable class used as return value in thread
	// thread safe to pass, exit and forget
	public class ThreadedProcessEventArgs : EventArgs 
	{
		private string consoleOutput;
		private string consoleError;
		private int exitCode;
		// constructor
		public ThreadedProcessEventArgs(string consoleOutput, 
			string consoleError,
			int exitCode) 
		{
			this.consoleOutput= consoleOutput;
			this.consoleError= consoleError;
			this.exitCode= exitCode;
		}
		// get only properties
		public string ConsoleOutput { get {return consoleOutput;}}
		public string ConsoleError { get {return consoleError;}}
		public int ExitCode {get {return exitCode;}}
	} // end ThreadedProcessEventArgs
	//
	// *** THREADED_PROCESS CLASS ***
	//
	// reusable class that launches a console application in a worker thread
	// implementing threaded I/O (cout, cerr, cin)
	// uses event of type threadedProcessEventHandler to callback client
	// a WinForms client must call BeginInvoke to marshall event to main WinForms thread
	public class ThreadedProcess
	{
		// public delegate used to send cerr, cout, and exitCode back to caller
public delegate void ThreadedProcessEventHandler(object worker, ThreadedProcessEventArgs e);
		// client passes delegate to StartInThread, setter throws if callhome == null
		private ThreadedProcessEventHandler callhome;
		private Process myProcess = new Process();
		private int waitForThreadTime= 1000;
		private int exitCode=-1;
		private int waitForExitTime= 1000;
		private string path= "";
		// internal flags
		// args may be null
		private string args= null;
		private bool isCoutDone= false;
		private bool isCerrDone= false;
		private bool isCinDone= false;
		private bool isClosed= false;
		private bool isStarted= false;
		private StreamReader my_cout;
		private StreamReader my_cerr;
		private StreamWriter my_cin;
		private Thread thread_cout;
		private Thread thread_cin;
		private Thread thread_cerr;
		private StringWriter your_cout= new StringWriter();
		private StringWriter your_cerr= new StringWriter();
		private StringReader your_cin= null;
		// public Properties
		public string Args 
		{
			set {this.args= value;} 
			get{return this.args;}
		}
		public string Path 
		{
			set {if (value != null) {path= value;}}
			get{return path;}
		}
		// takes an external immutable string, but uses text
		// internally as StringReader
		// so encapsulates conversion
		public string ConsoleInput 
		{
			set 
			{
				if (value == null) {value="";}
				this.your_cin= new StringReader(value);
			}		
		}
		public int WaitForThreadTime 
		{	
			set {if (value >0) {this.waitForThreadTime= value;}}
			get{return this.waitForThreadTime;}
		}
		public int WaitForExitTime 
		{	
			set {if (value >0) {this.waitForExitTime= value;}}
			get{return this.waitForExitTime;}
		}
		// return error string if process not done
		// externally appears as string
		// internally implemented as StringWriter
		public string ConsoleOutput 
		{
			get {
				if (!isStarted) 
				{
					return "Process not started.";
				}
				else if (!isClosed) 
				{
					return "Threads are not done reading.";
				}
				else {return your_cout.ToString();}
			}
		}
		public string ConsoleError 
		{
			get {
				if (!isStarted) 
				{
					return "Process not started.";
				}
				else if (!isClosed) 
				{ 
					return "Threads are not done reading.";
				}
				else {return your_cerr.ToString();}
			}
		}
		// caller could poll for IsClosed, but not recommended
		public bool IsClosed 
		{
			get {return isClosed;}
		}
		// console application exit code
		// -1 --> Process did not exit normally, had to force close
		// 0 --> Process did exit normally
		// 1 --> Process exited, but signalled error
		public int ExitCode 
		{
			get {
				if ((isStarted) && !isClosed) 
				{
					return -1;
				}
				else return exitCode;
			}
		}
		// I/O threads call these methods on exit
		// set flags to true, default is false
		private void notifyCoutDone() {isCoutDone= true;}
		private void notifyCerrDone() {isCerrDone= true;}
		private void notifyCinDone(){isCinDone= true;}
		// launch these I/O methods in separate threads
		// methods call Notify()when done
		private void cout_thread()
		{
			try 
			{
				// if process does not exit normally, will
				// throw exception on close
				while(!myProcess.HasExited) 
				{
					your_cout.WriteLine(my_cout.ReadLine());
				}
				your_cout.WriteLine(my_cout.ReadToEnd());
			}
			catch (Exception e) {}
			finally {notifyCoutDone();}
		}
		private void cin_thread() 
		{
			try 
			{
				// if process does not exit, will
				// throw exception on close
				while(!myProcess.HasExited) 
				{
					my_cin.WriteLine(your_cin.ReadLine());
				}
			}
			catch (Exception e) {}
			finally {notifyCinDone();}
		}
		private void cerr_thread() 
		{
			try 
			{
				// if process does not exit, will
				// throw exception on close
				while(!myProcess.HasExited) 
				{
					your_cerr.WriteLine(my_cerr.ReadLine());
				}
				your_cerr.WriteLine(my_cerr.ReadToEnd());
			}
			catch (Exception e) {}
			finally {notifyCerrDone();}
		}
		// not thread safe
		// ASSERT process is started, but not closed
		// code broken out for clarity only
		// ? must run in separate thread or will sleep I/O threads
		private void WaitForThreadExit(int waitForThreadTime) 
		{
			if (!isStarted || isClosed) {throw new Exception("Internal logic error.") ;}
			int elapsedTime=0;
			// exit if all I/O threads have returned or when times out
			while (elapsedTime <= waitForThreadTime) 
			{
				elapsedTime+= waitForThreadTime/10;
				Thread.Sleep(waitForThreadTime/10);
				if (isCinDone && isCerrDone && isCoutDone) 
				{
					break;
				}
			}
			System.Console.WriteLine(elapsedTime);
		}
		// ASSERT callhome != null
		// ASSERT isStarted != true;
		// Class is not thread safe, so throw exception if already started
		// Method is thread safe in that it will only launch once even
		// if two threads try to call this method "simultaneously"
		// Launch process in a worker thread so that method does not block
		// Need to use event to asynchronously notify client when
		// worker thread is done
		// If client is WinForms, client must call BeginInvoke in event handler
		public void StartInThread(ThreadedProcessEventHandler callhome) 
		{		
			if (callhome == null) {throw new ArgumentNullException();}
			// block here to avoid concurrency conflict
			// eg two threads trying to call StartInThread simultaneously
			lock(this) 
			{
				if (isStarted) {throw new Exception("Thread is running.");}
				this.isStarted= true;
			}
			this.callhome= callhome;
			// launch console app in new thread
			Thread thread_start= new Thread(new ThreadStart(Start_thread));
			thread_start.IsBackground= true;
			thread_start.Start();
		}
		private void Start_thread()
		{
			ProcessStartInfo myProcessStartInfo = 
				new ProcessStartInfo(path);
			myProcessStartInfo.UseShellExecute = false;
			myProcessStartInfo.RedirectStandardOutput = true;
			myProcessStartInfo.RedirectStandardError= true;
			myProcessStartInfo.RedirectStandardInput= true;
			myProcessStartInfo.CreateNoWindow= true;
			if (this.args != null) 
			{
				myProcessStartInfo.Arguments= args;
			}
			myProcess.StartInfo = myProcessStartInfo;
			try 
			{
				myProcess.Start();
			}
			catch(Exception ex) 
			{
				this.your_cout.WriteLine("");
				this.your_cerr.WriteLine(ex);
				this.isClosed= true;
				//callhome should never be null
				callhome(this,new ThreadedProcessEventArgs(this.ConsoleOutput,
						this.ConsoleError,
						this.ExitCode));
				return;
			}
			// redirect cin, cout, cerr
			my_cout= myProcess.StandardOutput;
			my_cerr= myProcess.StandardError;
			my_cin= myProcess.StandardInput;
			// launch thread threads in background for asynchronous I/O
			thread_cout= new Thread(new ThreadStart(this.cout_thread));
			thread_cout.IsBackground= true;
			thread_cin= new Thread(new ThreadStart(this.cin_thread));
			thread_cin.IsBackground= true;
			thread_cerr= new Thread(new ThreadStart(this.cerr_thread));
			thread_cerr.IsBackground= true;
			thread_cout.Start();
			thread_cerr.Start();
			thread_cin.Start();
			// wait until process exits or times out
			myProcess.WaitForExit(waitForExitTime);
			if (myProcess.HasExited) {exitCode= myProcess.ExitCode;}
			// else {exitCode= -1;} // timed out
			// wait until all three I/O theads exit or they time out
			this.WaitForThreadExit(this.waitForThreadTime);
			myProcess.Close();
			isClosed= true;
			// pass this since multiple threads may be calling home
			// caller can lock and invoke on this (free threaded caller)
			// or caller can call BeginInvoke to marshal call to main thread
			// (single threaded apartment)
			//setter throws if callhome == null
			callhome(this,new ThreadedProcessEventArgs(this.ConsoleOutput,
					this.ConsoleError,
					this.ExitCode));
		}
		// constructor
		public ThreadedProcess() 
		{
		}
	} // end ThreadedProcess

Download the Project

You can download the project here.

Have fun!

Send mail to [email protected] with questions or comments about this web site. Copyright © 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 © 
Last modified: 08/04/09
Hosted by www.Geocities.ws

1