How to pass objects between threads when running a System.Diagnostics process - c#

I won't provide all code, but an example of what I want to do. I have this code for updating GUI elements from an external processes stderr.
I set up my process like this:
ProcessStartInfo info = new ProcessStartInfo(command, arguments);
// Redirect the standard output of the process.
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.CreateNoWindow = true;
// Set UseShellExecute to false for redirection
info.UseShellExecute = false;
proc = new Process();
proc.StartInfo = info;
proc.EnableRaisingEvents = true;
// Set our event handler to asynchronously read the sort output.
proc.OutputDataReceived += new DataReceivedEventHandler(proc_OutputDataReceived);
proc.ErrorDataReceived += new DataReceivedEventHandler(proc_ErrorDataReceived);
proc.Exited += new EventHandler(proc_Exited);
proc.Start();
// Start the asynchronous read of the sort output stream. Note this line!
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
I then have an event handler
void proc_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
UpdateTextBox(e.Data);
}
}
Which invokes the following, which references a specific textbox control.
private void UpdateTextBox(string Text)
{
if (this.InvokeRequired)
this.Invoke(new Action<string>(this.SetTextBox), Text);
else
{
textBox1.AppendText(Text);
textBox1.AppendText(Environment.NewLine);
}
}
What I want is something like this:
private void UpdateTextBox(string Text, TextBox Target)
{
if (this.InvokeRequired)
this.Invoke(new Action<string, TextBox>(this.SetTextBox), Text, Target);
else
{
Target.AppendText(Text);
Target.AppendText(Environment.NewLine);
}
}
That I can use to update different Textboxes with from that thread, without having to create a seperate function for every control in the GUI.
Is this possible? (obviously the code above does not work correctly)
Thanks.
UPDATE:
private void UpdateTextBox(string Text, TextBox Target)
{
if (this.InvokeRequired)
this.Invoke(new Action<string, TextBox>(this.**UpdateTextBox**), Text, Target);
else
{
Target.AppendText(Text);
Target.AppendText(Environment.NewLine);
}
}
This code does appear to work now as I noticed a typo.. is this ok to use?

The code you provided looks good and is good way to send such messages between threads.

Take a look at this.
http://weblogs.asp.net/justin_rogers/pages/126345.aspx
I have done this before, but I don't have the code with me right now.
if I get to it I will post it for you, but the article might have enough information for you to figure out.

Related

Is there any event to tell me a process is waiting for an input?

I am processing a Tex file by starting a Process like this one:
process p1 = new Process();
p1.StartInfo.FileName = "C:\\texlive\\2012\\bin\\win32\\pdflatex.exe";
p1.StartInfo.Arguments = FileName;
p1.consuleProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p1.consuleProcess.StartInfo.CreateNoWindow = true;
p1.consuleProcess.StartInfo.RedirectStandardOutput = true;
p1.consuleProcess.StartInfo.UseShellExecute = false;
p1.consuleProcess.StartInfo.RedirectStandardInput = true;
p1.Start();
p1.consuleProcess.BeginOutputReadLine();
p1.consuleProcess.OutputDataReceived += new DataReceivedEventHandler(p1_OutputDataReceived);
I display the output strings in a TextBox by handling OutputDataReceived event.
If there were an error in the Tex file, a line should be written in StandardInput. I think there is no event that can tell me, when the process is waiting for an input; So I thought, I can check OutputDataReceived event to see when the condition: e.Data == "?" is true. But, the problem is that the StandardInput needs an input, just before firing OutputDataReceived event with e.Data=="?"
So, what can I do to see when the process is waiting for an input?
thanks.
Not sure if this is what you mean but are you after something like
p1.WaitForInputIdle(NumberofMilisecondstoWait)
Update:
Maybe something like this
public void test()
{
p1.OutputDataReceived += new DataReceivedEventHandler(p1_OutputDataReceived);
}
void p1_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
throw new NotImplementedException();
}

How do you start a batch file in the background and redirect its output?

I am trying to call a batch file from an application, but I want the command window to be hidden and the standard output to be redirected to one or more locations (as it is produced by the batch file).
My issue is that when the batch file is running the console is up and nothing displays; it's just up. When the task completes, the console closes. I want to get rid of the console (perhaps have it run in the back ground).
The other issue is that I am redirecting the output to a richtext box. If i redirect it to the console or text box, it just spits out all the results at once. I would like it to spit out line by line as it happens. Make sense?
The code is below:
//Declare and instantiate a new process component.
System.Diagnostics.Process process1;
process1 = new System.Diagnostics.Process();
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.RedirectStandardOutput = true;
process1.StartInfo.FileName = "cmd.exe";
process1.StartInfo.Arguments = "<BATCHfILE>";
process1.Start();
string output = process1.StandardOutput.ReadToEnd();
rchsdtOut.Text = output;
Console.WriteLine(process1.StandardOutput.ReadToEnd());
process1.WaitForExit();
process1.Close();
This is how I would have done it. Hope I understood your question correctly:
Add:
process1.CreateNoWindow = true,
process1.OutputDataReceived += (s, e) => myMethod(e);
process1.BeginOutputReadLine();
And then a method
private void myMethod(DataReceivedEventArgs e)
{
//Do something with e.Data
}
To solve the Cross-thread operation issue mentioned in the comments. You will need to add this to your form class (before the functions begin):
private delegate void updateText(string str);
Then you need to add this:
private void update_richTextBox1(string value)
{
richTextBox1.Text += value;
}
And then in the myMethod function add:
richTextBox1.Invoke(new updateText(update_richTextBox1), new object[] { e.Data.ToString() });

Cross-thread operation not valid: Control 'rchsdtOut' accessed from a thread other than the thread it was created on

So I am getting this cross thread error and I cannot figure it out. Here is my base code before I attempted to muck around with it.
Bascally what the code is going is calling a batch file which then calls a java file. The outputdata is then redirected to the console in real time. When I just redirect the output just to the C# console, it works fine. But I want the same info to print out into a rich text box within the app. VS 2010 complaines at rchsdtOut.Text = e.Data.ToString();
that Cross-thread operation not valid: Control 'rchsdtOut' accessed from a thread other than the thread it was created on.
I have tried looking this up, and I do admit I am new to threading, so any help on how to easy accomplish this would be appreciated.
//Declare and instantiate a new process component.
System.Diagnostics.Process process1;
process1 = new System.Diagnostics.Process();
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.RedirectStandardOutput = true;
process1.StartInfo.CreateNoWindow = true;
process1.StartInfo.FileName = "cmd.exe";
process1.StartInfo.Arguments = "BATFile.bat";
process1.OutputDataReceived += (s, a) => myMethod(a);
process1.Start();
process1.BeginOutputReadLine();
process1.WaitForExit();
process1.Close();
private void myMethod(DataReceivedEventArgs e) {
if (e.Data != null)
{
rchsdtOut.Text = e.Data.ToString();
Console.WriteLine(e.Data.ToString());
}
}//end of private
Try this line:
process1.OutputDataReceived += (s, a) => rchsdtOut.Invoke(new System.Action(()=> myMethod(a)));
It's not legal to access a WinForms control from a thread other than the one it was created on. You need to use Invoke or BeginInvoke to get control back to the appropriate thread.
private void myMethod(DataReceivedEventArgs e) {
if (e.Data != null) {
Action action = () => rchstdOut.Text = e.Data.ToString();
rchstdOut.Invoke(action, null);
Console.WriteLine(e.Data.ToString());
}
}
You cannot access a Form Control from a thread other then where it was created.
You will need to create some other object that both of the threads can access.
Producer Consumer Problem
Well rchsdtOut can only be upodated from the UI thread while your method is called from another.
There are several solutions. If you want to have a general method to update controls you can check the control.InvokeRequired property (or this.Dispatcher.CheckAccess() in WPF) and use a delegate.
private delegate void UpdateTextControlDelegate(Control control, string text);
private void UpdateTextControl(Control control, string text)
{
if (control.InvokeRequired)
{
Invoke(new UpdateTextControlDelegate(UpdateTextControl), new object[] { control, text});
return;
}
control.Text = text;
}
...
if (e.Data != null)
{
UpdateTextControl(rchsdtOut, e.Data.ToString());
Console.WriteLine(e.Data.ToString());
}

Waiting for commands to be complete

I am working with a winform that runs a cmd in the background, redirecting input and output asynchronously.
Currently, the winform iterating through an array of commands, writing each to the cmd via the StreamWriter the StandardInput is redirected to. How can I force the loop to wait until the present command is complete in the cmd before writing the next line in?
EDIT: I took out all of my actual project code, and replaced it with this, a stripped down version of what I'm trying to do, only including components of my project relevant to my question.
public partial class Form1 : Form
{
public delegate void WriteToConsoleMethod(string text);
Process _process;
string[] _commands =
{
"echo hello world",
"echo my name is T.K.",
"echo Here is a list of commands"
};
public Form1()
{
InitializeComponent();
ProcessStartInfo processStartInfo = new ProcessStartInfo("cmd")
{
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
_process = Process.Start(processStartInfo);
_process.OutputDataReceived += new DataReceivedEventHandler(new DataReceivedEventHandler(DataReceived_EventHandler));
_process.ErrorDataReceived += new DataReceivedEventHandler(new DataReceivedEventHandler(DataReceived_EventHandler));
_process.BeginErrorReadLine();
_process.BeginOutputReadLine();
}
private void DataReceived_EventHandler(object sender, DataReceivedEventArgs e)
{
IAsyncResult result = this.BeginInvoke(new WriteToConsoleMethod(writeToConsole), new object[] { e.Data + Environment.NewLine });
this.EndInvoke(result);
}
private void writeToConsole(string output)
{
txtbxConsole.AppendText(output);
}
private void btnBegin_Click(object sender, EventArgs e)
{
foreach (string command in _commands)
{
_process.StandardInput.WriteLine(command);
// I want a way to pause here until the cmd has finished processing the command.
}
}
}
I don't think there is anything built-in that will support that. However you could send your own special command and then wait until you see this in the output for example ,
something like :
const string Separator= "---Command Completed--\xE3\xE2\xE1\xE0\xE3";
// Has to be something that won't occur in normal output.
volatile bool finished = false;
private void button1_Click(object sender, EventArgs e)
{
foreach (string command in _commands)
Run(command);
}
private void writeToConsole(string output)
{
if (output.IndexOf(Separator) >= 0)
finished = true;
else
richTextBox1.AppendText(output);
}
private void Run(string command)
{
finished = false;
_process.StandardInput.WriteLine(command);
_process.StandardInput.WriteLine("#echo " + Seperator);
while (!finished)
{
Application.DoEvents();
System.Threading.Thread.Sleep(100);
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
finished = true;
}
Assuming you are using System.Diagnostics.Process, then you probably need something like
ProcessStartInfo pi = new ProcessStartInfo(cmd);
pi.Arguments = ...
pi.WorkingDirectory = ...
Process myProcess = Process.Start(pi);
myProcess.WaitForExit();
I ended up solving this problem by wrapping my interaction with the command prompt into a separate class and instead of maintaining one prompt for all of the actions, I started up another prompt for each call. Then I take advantage of WaitForExit() to synchronize my threads.
After each command, I write in an exit command to close the process. I scan the output for exit calls, and when I find one, I use the context of that line to save the workspace so that the prompt for the next command will be made from the same working directory. I also had to hook up a DataRecievedEventHandler to parse out the header and exit calls before forwarding the EventHandlers to the winform.
The thing that's nagging me about this solution is that if the output of whatever process I'm running prints out exit, output scanner will behave as though it found the original exit. I employed the same solution sgmoore had in his answer - I write in exit [UNIQUE STRING] to the prompt, and scan the output for that, but I'm sure that's far from best practice.

Read from console process

I have a process, i can start, and hide working fine, but i want to read from the console program, when i runs, not after, i tried to run a timer, anbd read at the tick, but my program just crashes and when it not do, i get nothing at all.
startInfo= new ProcessStartInfo("cmd.exe");
startInfo.Arguments ="/C uus.exe "+ arg.ToString();
startInfo.RedirectStandardError = true;
startInfo.RedirectStandardOutput = true;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
this.timer1.Enabled=true;
this.listBox1.Items.Clear();
p= Process.Start(startInfo);
Application.DoEvents();
void Timer1Tick(object sender, EventArgs e)
{
string str="";
str=p.StandardOutput.ReadLine();
if(str != null)
{
this.Text=str.ToString();
this.listBox1.Items.Add(str);
}
Application.DoEvents();
}
So what do i do to solve this?
Update:
I tried bender suggestion
now My program don't crash anymore, but also don't recvie any data
proc.StartInfo.UseShellExecute=false;
proc.StartInfo.CreateNoWindow=true;
proc.StartInfo.RedirectStandardOutput=true;
proc.StartInfo.RedirectStandardError=true;
proc.StartInfo.FileName="uus.exe";
proc.StartInfo.Arguments=arg;
proc.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(SortOutputHandler);
proc.Start();
proc.BeginOutputReadLine();
void SortOutputHandler(object o,System.Diagnostics.DataReceivedEventArgs e)
{
string str="";
string str2="";
str=e.Data.ToString();
if(str!=null && str!="")
{
this.listBox1.Items.Add(str.ToString());
this.Text=str.ToString();
}
str2=proc.StandardOutput.ReadLine();
if(str2!=null && str2!="")
{
this.lsw1.Items.Add(str2.ToString());
}
}
hmm?
Update:
I have changed the handler, because i have being tell, it can't do it, that it wil be cross thread operation, usualyy i wille have get an error if it was.
private delegate void TextAdderDelegate(string str);
void TextAdder(string str)
{
if(this.lsw1.InvokeRequired==true)
{
Invoke(new TextAdderDelegate(TextAdder),new object[] {str});
}
else
{
this.lsw1.Items.Add(str);
}
}
void SortOutputHandler(object o,System.Diagnostics.DataReceivedEventArgs e)
{
string str="";
if(e!=null)
{
if(e.Data!=null)
{
str=e.Data.ToString();
}
}
TextAdder(str);
}
The problem is that you're running on one thread and trying to write using another. When you created your background thread using the Timer's tick event, it can't have frontend user input.
Perhaps if you explained the big picture of what you're trying to accomplish, we can better help you.
In the meantime, you might want to create threadsafe writes. This article will help you to understand the problem and solution to writing to form controls on different threads.
You may create the Process instance explicitly (e.g. new Process)and use the OutputDataReceived event, the method BeginOutputReadLine() and, when finished CancelOutputRead() for that.
The event OutputDataReceived will be repeatedly called asynchronously from a different thread as soon output data is available.
I assume you get an 'thread cross exception', this may be caused because you're updating your form controls on an other thread then the UI thread.

Categories