Reputation: 152
i'm challanging myself with something that is difficult for me..
I would like to have a "Master terminal" that runs "slave terminals". The slave are started with the "Redirect output" option (RedirectStandardOutput). The slave must run async and the Master should manage the "ReadLine" from the slave. While the slave are running, the Master must be able to execute other actions.
My actual code:
static void StartTerminal(string ExecPath, int DefaultPort, string Arguments = "")
{
int port = DefaultPort;
if (usedPorts.Contains(port)) port = nextPort;
while(usedPorts.Contains(nextPort))
{
nextPort++;
port = nextPort;
}
usedPorts.Add(port);
string _arguments = "/port:" + port;
_arguments += Arguments;
//* Create your Process
Process process = new Process();
process.StartInfo.FileName = "dotnet ";
process.StartInfo.Arguments = ExecPath + " /c " + _arguments;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
//* Set your output and error (asynchronous) handlers
process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
process.ErrorDataReceived += new DataReceivedEventHandler(OutErrorHandler);
//* Start process and handlers
ThreadStart ths = new ThreadStart(() => {
bool ret = process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
});
Thread th = new Thread(ths);
th.Start();
//process.Start();
//process.BeginOutputReadLine();
//process.BeginErrorReadLine();
//process.WaitForExit();
}
As you can see, i tried both with and without starting a new Thread. With the thread seems fine, i reach the end of the Main void before the slave terminal has completed the operations but i'm then blocked on the request of "ReadLine" of the slave. The user must press Enter to continue and i don't want this..
Can you help me please? Do you suggest a different approach?
Thanks, regards.
Edit 1:
I want to manage the condition where the Slave Terminal is waiting for an "Enter" because it could be developed by 3rd parties that could ignore my guidelines where i say to do not use Console.ReadLine()
(ok, is not my problem but i like stupid proof software).
The Master Terminal is managing an undefined number of Slave Terminals and logging all the output of the Slaves plus running a Watchdog that restarts a Slave if it crash.
The Master is also receiving commands to start/stop/reboot a Slave and/or other commands.
Upvotes: 3
Views: 1252
Reputation: 1646
Both Process.BeginOutputReadLine();
and Process.BeginErrorReadLine();
execute asynchronously
, according to Process Class documentation, which means they do not block the program execution (or in other words: the program does not wait for those methods to finish executing).
You probably want to set some booleans to indicate when data has finished reading in OutputDataReceived
and ErrorDataReceived
event handlers and in main method add a loop that would not allow the program to finish until both methods are executed.
private void DataReceivedEventHandler( [parameters] ){
//I believe this is the part where you read the actual stream
outputDataReceived = true;
}//do the same for ErrorDataReceived
while (!outputDataReceived && !errorDataReceived){
wait(1000); //The actual method might be different, maybe sleep( 1000 ) or thread.sleep ( you can also set a different interval )
}
Edit (Irrelevant edits removed): Asynchronous tasks,treads and Parallel execution are described in answers of this question. I believe any one of the answers will suit your needs.
Basically, use any of the code samples in the answers of the linked question, just call your StartTerminal( )
method in the thread / task / parallel invocation. Use the code in the answers to create asynchronous processing and implement logic to prevent program from reaching its end until all threads have finished doing their work. Pseudo code:
List<Thread> threads = new List<Thread>();
private void StartTerminal(int id, params){
//all of your code keep it asynchornous
while( !outputDataReceived && !errorDataReceived ){
sleep( 1000 ); //delay between checks
} //Makes sure this thread does not close until data is received, implement any other logic that should keep the thread alive here
}
public static void main(...){
foreach(ParameterSet params in Parameters){ //Create thread list with different parameters
var thread = new Thread( StartTerminal(params) );
threads.Add( thread );
}
while( !threads.isEmpty() ) ){ //if it is empty, all threads finished the job and got deleted, loop is skipped and program closes
var threadsToRemove = new List<Thread>();
foreach(Thread t in threads){ //start unstarted threads
if(t.ThreadState == ThreadState.Unstarted){
threads[i].Start( ).
}elseif(t.ThreadState == ThreadState.Stopped){ //remove stopped threads
threadsToRemove.Add(t).
}//Implement other logic for suspended, aborted and other threads...
}
foreach(Thread t in threadsToRemove){
threads.Remove(t);
}
sleep(1000); //delay between checks
}
}
EDIT 2: Give these patterns to your slave terminal developers (if they don't know how to do it themselves, without blocking everything), because once 'ReadLine' is called synchronously, there is nothing (reasonable) you can do until the user presses enter.
When possible (especially when programming hardware, such as controllers) use triggers instead of 'ReadLines' for user inputs
HardwareButtonEnter.Press += delegate{ userInput = eventParameters.userInput;
UserInputReceived = true;}; //This is really hardware API specific code, only the boolean is important
//start an async thread/process for reading master terminal input.
while(1 = 1){
if(UserInputReceived){
ProcessUserInput().
}elseif(masterTerminalDataReceived){
ProcessMasterTerminalData().
}
}
If you cannot use triggers (for example when programming a console application) put blocking statements in asynchronous threads and process them when threads finish.
//Start your (async) ReadLine thread. Make sure it also sets UserInputReceived = true when it ends
//Start your async process read thread. also get it to set some boolean.
while( 1 = 1){
if(threads.Any( t => t.Status = Finished ){
if(UserInputReceived){
}elseif(terminalInputReceived){
..
}
}
}
Upvotes: 2