In a Windows Forms project I have a handler for a button that opens a file in Notepad for editing. Once notepad closes I call a function RefreshTextBox() to parse the text file and update a TextBox based on a value. Here is the method that opens Notepad and calls the refresh method once its closed:
private void button_Click(object sender, EventArgs e)
{
Process p = new Process
{
EnableRaisingEvents = true,
StartInfo =
{
FileName = "NOTEPAD.EXE",
Arguments = _path,
WindowStyle = ProcessWindowStyle.Maximized,
CreateNoWindow = false
}
};
p.Exited += (a, b) =>
{
RefreshTextBox();
p.Dispose();
};
p.Start();
}
And code to refresh the textbox:
private void RefreshTextBox()
{
using (StreamReader reader = File.OpenText(_appSettingsPath))
{
string text = reader.ReadToEnd();
// Code to parse text looking for value...
// InvalidOperationException thrown here:
textBox.Text = reader.Value.ToString();
}
}
This throws an Exception for trying to update the Control from a thread other than the one it was created on. I'm having trouble understanding why though. I'm not doing this in a new task or backgroundworker or anything like that. Obviously notepad is running in another thread, but the refresh method isn't called until after it's process has exited.
Edit: I should add that this error throws up a Fatal Exception popup when debugging in Visual Studio (as an Admin). It doesn't show the popup when running the application on its own, either the exception is silently swallowed or it doesn't occur then.
As per documentation if Process SynchronizingObject is not set it will execute exited event in system threadpool to avoid this and run that event handler in UI thread you need to set SynchronizingObject to Form Instance
When SynchronizingObject is null, methods that handle the Exited event are called on a thread from the system thread pool. For more information about system thread pools, see ThreadPool.
If you set
p.SynchronizingObject = WindowsFormName;
Then it will run in same thread or it will execute in a system threadpool thread which will cause crossthread exception.
MSDN Reference
private void button_Click(object sender, EventArgs e)
{
Process p = new Process
{
EnableRaisingEvents = true,
StartInfo =
{
FileName = "NOTEPAD.EXE",
Arguments = _path,
WindowStyle = ProcessWindowStyle.Maximized,
CreateNoWindow = false
}
};
//p.SynchronizingObject = this;
p.Exited += (a, b) =>
{
RefreshTextBox();
p.Dispose();
};
p.Start();
}
private void RefreshTextBox()
{
using (StreamReader reader = File.OpenText(_appSettingsPath))
{
string text = reader.ReadToEnd();
// Code to parse text looking for value...
//textBox.Text = text; // reader.Value.ToString();
threadSafeControlUpdate(textBox, text);
}
}
public delegate void updateUIfunc(Control c, object v);
public void threadSafeControlUpdate(Control c, object v)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new updateUIfunc(threadSafeControlUpdate), c, v);
return;
}
if (c is TextBox && v is string)
{
c.Text = (string)v;
}
}
I would recommend capturing the synchronization context and posting the RefreshTextBox call onto it. Something like:
private void button_Click(object sender, EventArgs e)
{
var _synchronizationContext = WindowsFormsSynchronizationContext.Current;
Process p = new Process
{
EnableRaisingEvents = true,
StartInfo =
{
FileName = "NOTEPAD.EXE",
Arguments = _path,
WindowStyle = ProcessWindowStyle.Maximized,
CreateNoWindow = false
}
};
p.Exited += (a, b) =>
{
_synchronizationContext.Post(_=> RefreshTextBox(), null);
p.Dispose();
};
p.Start();
}
Related
I am starting NodeJs as a process inside a C# application. My intent is to restart the process every time it stops.
Code for starting the process is:
_nodeProcess = new Process
{
StartInfo =
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
WorkingDirectory = location,
FileName = "node.exe",
Arguments = "main.js"
}
};
_nodeProcess.EnableRaisingEvents = true;
_nodeProcess.Exited += nodeExited;
_nodeProcess.Start();
string stderrStr = _nodeProcess.StandardError.ReadToEnd();
string stdoutStr = _nodeProcess.StandardOutput.ReadToEnd();
if (!String.IsNullOrWhiteSpace(stderrStr))
{
LogInfoMessage(stderrStr);
}
LogInfoMessage(stdoutStr);
_nodeProcess.WaitForExit();
_nodeProcess.Close();
here is nodeExited method:
private void nodeExited(object sender, EventArgs e)
{
if (!_isNodeStop)
{
this.restartERM_Click(sender, e);
}
else
{
_isNodeStop = false;
}
}
_isNodeStop is just a flag that I set to true when killing a node from the controlled place.
Like this:
private void KillNode()
{
foreach (var process in Process.GetProcessesByName("node"))
{
_isNodeStop = true;
process.Kill();
}
}
My problem is that nodeExited method does not trigger every time node is stopped. I have no clue why and I could not see any pattern. Is just does not stop most of the times.
You are using WaitForExit() anyway, so there is no reason to use the Exited event.
Just manually call your Handler after WaitForExit() like this:
_nodeProcess.WaitForExit();
_nodeProcess.Close();
nodeExited(_nodeProcess, new EventArgs());
and remove
_nodeProcess.EnableRaisingEvents = true;
_nodeProcess.Exited += nodeExited;
Edit:
If I understand this answer correctly, you might also have a deadlock because you call StandardError.ReadToEnd(); and then StandardOutput.ReadToEnd();. The StandardOutput Buffer might be full before it even reaches that point.
For all too long, I have been trying to run an external .bat file (calls an R script for some statistical processing), and have the console redirect to the U.I.
I think I am close, but just as I have gotten it to work I have run into a sizable problem! That is: it only bloody works once the main thread has ended (via: return;), and not during Thread.Sleep, or .WaitOne() or etc.
Here is my code in the main thread.
string batLoc = ALLRG___.RSCRBIN_LOC + "current.bat";
BackgroundWorker watchboxdWorker1 = new BackgroundWorker();
watchboxdWorker1.DoWork += frmC.WatchboxWorker1_WatchExt;
frmC.wbResetEvent = new AutoResetEvent(false);
watchboxdWorker1.RunWorkerAsync(batLoc);
//Thread.Sleep(1000*20);
//frmC.wbResetEvent.WaitOne();
return;
Note the commented out Sleep and/or WaitOne() instructions. If I try and use these the BackgroundWorker DOES execute, but the 'events' which update the U.I do not.
The code in my form (frmC above) is as follows,
public void WatchboxWorker1_WatchExt(object sender, DoWorkEventArgs e)
{
string exeLoc = (string) e.Argument;
string arg1 = exeLoc;
string arg2 = "";
ProcessStartInfo pStartInfo = new ProcessStartInfo();
pStartInfo.FileName = exeLoc;
pStartInfo.Arguments = string.Format("\"{0}\" \"{1}\"", arg1, arg2);
pStartInfo.WorkingDirectory = Path.GetDirectoryName(exeLoc);
pStartInfo.CreateNoWindow = true;
pStartInfo.UseShellExecute = false;
pStartInfo.RedirectStandardInput = true;
pStartInfo.RedirectStandardOutput = true;
pStartInfo.RedirectStandardError = true;
Process process1 = new Process();
process1.EnableRaisingEvents = true;
process1.OutputDataReceived += new DataReceivedEventHandler(wbOutputHandler);
process1.ErrorDataReceived += new DataReceivedEventHandler(wbErrorHandler);
process1.StartInfo = pStartInfo;
process1.SynchronizingObject = rtbWatchbox;
process1.Start();
process1.BeginOutputReadLine();
process1.BeginErrorReadLine();
process1.StandardInput.Close();
process1.WaitForExit();
wbResetEvent.Set();
}
public void wbOutputHandler(Object source, DataReceivedEventArgs outLine)
{
int x = 0;
if (!String.IsNullOrEmpty(outLine.Data))
{
rtbWatchbox.AppendText(outLine.Data);
}
}
public void wbErrorHandler(Object source, DataReceivedEventArgs outLine)
{
int x = 0;
if (!String.IsNullOrEmpty(outLine.Data))
{
rtbWatchbox.AppendText(outLine.Data);
}
}
My problem is --
The wbOutputHandler and wbErrorHandler get fired as the console updates nicely - but only when the main thread has exited (using the return;).... if I use the Thread.Sleep or .WaitOne() in the main thread to pass control to the BackgroundWorker (WatchboxWorker1_WatchExt), then the code runs successfully, but the wbOutputHandler and wbErrorHandler methods do not get triggered at all.
In fact, if I do the Thread.Sleep(10*1000), then the external program starts running as planned, 10 seconds pass, then when the main UI thread exits I get a whole big enormous update all at once.
I don't want to have my main thread closed, I want to keep doing stuff there after the Worker is finished!
[ of course happy for alternate methods that are a better approach ]
"Help me Stack Overflow, you are my only hope!"
The answer was to put a backgroundWorker within another backgroundWorker, which is created for the UI Thread. I thought quite complicated given the reletivly simple requirement of printing a console output to the UI!
I now call my functions from the UI as follows -
private void btInsertBCModls_Click(object sender, EventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += RC2___Scratchpad4.BC_RunExistingBCModel;
bw.RunWorkerAsync(this);
}
Next I use the delegate & Invoke method on any richTextBox I need to update from another thread -
delegate void UpdateWriteboxCallback(String str);
public void wbWriteBox(string WriteString)
{
if (!String.IsNullOrEmpty(WriteString))
{
if (rtbWatchbox.InvokeRequired)
{
UpdateWriteboxCallback at = new UpdateWriteboxCallback(wbWriteBox);
this.Invoke(at, new object[] { WriteString });
}
else
{
// append richtextbox as required
}
}
}
Then from within my function I use another BackgroundWorker to run the console stuff -
public static void BC_RunExistingBCModel(object sender, DoWorkEventArgs e)
{
RC2___RhinegoldCoreForm frmC = e.Argument as RC2___RhinegoldCoreForm;
BackgroundWorker watchboxWorker = new BackgroundWorker();
watchboxWorker.DoWork += frmC.WatchboxWorker_RunProc;
watchboxWorker.RunWorkerAsync(batLoc);
while (watchboxWorker.IsBusy)
Thread.Sleep(50);
frmC.UpdateRGCoreStatusBox4("Executed script " + m + "... ");
}
Which in turn, in the DoWork function, calls the wbWriteBox function above.
public void WatchboxWorker_RunProc(object sender, DoWorkEventArgs e)
{
string exeLoc = (string) e.Argument;
string arg1 = exeLoc;
string arg2 = "";
ProcessStartInfo pStartInfo = new ProcessStartInfo();
pStartInfo.FileName = exeLoc;
pStartInfo.Arguments = string.Format("\"{0}\" \"{1}\"", arg1, arg2);
pStartInfo.WorkingDirectory = Path.GetDirectoryName(exeLoc);
pStartInfo.CreateNoWindow = true;
pStartInfo.UseShellExecute = false;
pStartInfo.RedirectStandardInput = true;
pStartInfo.RedirectStandardOutput = true;
pStartInfo.RedirectStandardError = true;
Process process1 = new Process();
process1.EnableRaisingEvents = true;
process1.OutputDataReceived += (s, e1) => this.wbWriteBox(e1.Data);
process1.ErrorDataReceived += (s, e1) => this.wbWriteBox(e1.Data);
process1.StartInfo = pStartInfo;
process1.SynchronizingObject = rtbWatchbox;
process1.Start();
process1.BeginOutputReadLine();
process1.BeginErrorReadLine();
process1.StandardInput.Close();
process1.WaitForExit();
//wbResetEvent.Set();
}
Phew! A tricky solution to an easily defined problem. If someone has a better way, let me know.
And thanks to Carsten for all the help - magnificent.
In the Form load I have this code which sets up a callback function to be called every 10 secs
private void Form1_Load(object sender, EventArgs e)
{
var timer = new System.Threading.Timer(_ => updateMethod(), null, 0,
10*1000);
}
In the updateMethod I start a new process to launch a legacy exe and read the output
private void updateMethod()
{
string console_exe = "MyLegacy.Exe";
Process prcss = new Process();
prcss .StartInfo.UseShellExecute = false;
prcss .StartInfo.RedirectStandardOutput = true;
prcss .StartInfo.FileName = console_exe;
prcss .StartInfo.Arguments = URL + " list";
prcss .Start();
prcss .WaitForExit();
string output = prcss.StandardOutput.ReadLine();
while (null != output)
{
output = prcss.StandardOutput.ReadLine();
}
}
updateMethod() is called exactly twice when I run my application.
After that it is just not called.
In the updateMethod() if I return before calling the new Process, the function gets called repeatedly until I close the application.
Don't understand why this method is called twice but not after that.
Is there another way of doing this instead of new Process() ?
while the execution of a time consuming python script , i would manage the IU with background worker to display a progress bar.
i have used the background worker successfully when i needn't the event OutputDataReceived , but the script that i'm using prints some progress values like ("10" , "80",..), so i got to listen the event OutputDataReceived.
i get this error : This operation has already had OperationCompleted called on it and further calls are illegal. in this line progress.bw.ReportProgress(v);.
i tried to use 2 background worker instances, one executes and the other listens , it gives no errors but it seems do not call the event 'OutputDataReceived' so i don't see any progress in the progress bar.
below the code that i used:
private void execute_script()
{
progress.bw.DoWork += new DoWorkEventHandler( //progress.bw is reference to the background worker instance
delegate(object o, DoWorkEventArgs args)
{
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo.FileName = "python.exe";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.Arguments = #".\scripts\script1.py " + file_path + " " + txtscale.Text;
//proc.StartInfo.CreateNoWindow = true;
//proc.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
proc.StartInfo.RedirectStandardOutput = true;
//proc.EnableRaisingEvents = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.RedirectStandardError = true;
proc.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(proc_OutputDataReceived);
proc.Start();
proc.BeginOutputReadLine();
//proc.WaitForExit();
//proc.Close();
});
progress.bw.RunWorkerAsync();
}
///the function called in the event OutputDataReceived
void proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
//throw new NotImplementedException();
if (e.Data != null)
{
int v = Convert.ToInt32(e.Data.ToString());
MessageBox.Show(v.ToString());
// report(v);
progress.bw.ReportProgress(v);
}
else
MessageBox.Show("null received");
}
The problem is that the BackgroundWorker's DoWork handler finishes as soon as the process starts, as there's nothing "waiting" (since you commented out proc.WaitForExit()) for the process to finish. Once the BackgroundWorker work handler completes, you can no longer report progress using that instance.
Since Process.Start is already asynchronous, there is no reason to use a background worker at all. You can just marshal the call from OutputDataReceived onto the UI thread yourself:
///the function called in the event OutputDataReceived
void proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
//throw new NotImplementedException();
if (e.Data != null)
{
int v = Convert.ToInt32(e.Data.ToString());
// MessageBox.Show(v.ToString());
// progress.bw.ReportProgress(v);
this.BeginInvoke( new Action( () => {
this.progressBar.Value = v;
}));
}
}
If you use this, don't create the BackgroundWorker at all.
BackGroundWorker has a ReportProgress option that is built just for this.
BackgroundWorker.ReportProgress Method (Int32, Object)
I have an app which calls another process in a command window and that process has updating stats that output to the console window. I thought this was a fairly simple operation but I can't seem to get it to work. Am I missing something?
string assemblyLocation = Assembly.GetExecutingAssembly().Location;
Process process = new Process
{
ProcessStart =
{
RedirectStandardOutput = true,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = arg,
FileName = assemblyLocation.Substring(0, assemblyLocation.LastIndexOf("\\")) + "\\ffmpeg.exe",
CreateNoWindow = true
}
};
process.Start();
Console.WriteLine(process.StandardOutput.ReadToEnd());
process.WaitForExit();
Ideally what I would like is as the output changes within that process I hit or data comes into the reader that I get events off it.
Any help would be great, I feel like this is a newbie question but seem to be missing something.
I've experienced this before. Sometimes, the way in which the process you're calling outputs to the console is not compatible with this sort of output redirection. I've been fortunate enough in this case to be able to modify the external process to get around this.
You might try running your code on another process that outputs to the console, and see if it works properly. It reads about right to me right now.
EDIT:
I went and pulled a code block I've used to do this. This is in a WPF app which redirects the process output to the window. Notice the event binding. Since this is WPF I have to invoke my call to write the data out. Since you aren't worried about blocking, ou should be able to simply replace that with:
Console.WriteLine(e.Data);
Hopefully it helps!
private static void LaunchProcess()
{
Process build = new Process();
build.StartInfo.WorkingDirectory = #"dir";
build.StartInfo.Arguments = "";
build.StartInfo.FileName = "my.exe";
build.StartInfo.UseShellExecute = false;
build.StartInfo.RedirectStandardOutput = true;
build.StartInfo.RedirectStandardError = true;
build.StartInfo.CreateNoWindow = true;
build.ErrorDataReceived += build_ErrorDataReceived;
build.OutputDataReceived += build_ErrorDataReceived;
build.EnableRaisingEvents = true;
build.Start();
build.BeginOutputReadLine();
build.BeginErrorReadLine();
build.WaitForExit();
}
// write out info to the display window
static void build_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
string strMessage = e.Data;
if (richTextBox != null && !String.Empty(strMessage))
{
App.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Send, (ThreadStart)delegate()
{
Paragraph para = new Paragraph(new Run(strMessage));
para.Margin = new Thickness(0);
para.Background = brushErrorBrush;
box.Document.Blocks.Add(para);
});
}
}
I'm not sure exactly what problem you're running into, but if you're looking to act on output as soon as it's generated, try hooking into the process's OutputDataReceived event. You can specify handlers to receive output asynchronously from the process. I've used this approach successfully.
Process p = new Process();
ProcessStartInfo info = p.info;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
p.OutputDataReceived += p_OutputDataReceived;
p.ErrorDataReceived += p_ErrorDataReceived;
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
p.WaitForExit();
..
void p_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine("Received from standard out: " + e.Data);
}
void p_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine("Received from standard error: " + e.Data);
}
See the OutputDataReceived event off Process for more information.
Using lambda expressions, etc:
var info = new ProcessStartInfo(path)
{
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false,
Verb = "runas",
};
var process = new Process
{
EnableRaisingEvents = true,
StartInfo = info
};
Action<object, DataReceivedEventArgs> actionWrite = (sender, e) =>
{
Console.WriteLine(e.Data);
};
process.ErrorDataReceived += (sender, e) => actionWrite(sender, e);
process.OutputDataReceived += (sender, e) => actionWrite(sender, e);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
Interestingly you can't read from standard output and standard error at the same time:
if you redirect both standard output and standard error and then try to read both, for
example using the following C# code.
[C#]
string output = p.StandardOutput.ReadToEnd();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
In this case, if the child process writes any text to standard error it will block the
process, because the parent process cannot read from standard error until it has finished
reading from standard output. However, the parent process will not read from standard
output until the process ends. A recommended solution to this situation is to create two
threads so that your application can read the output of each stream on a separate thread.
http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.redirectstandardoutput(v=vs.71).aspx
flowing code worked in VS2010
void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (String.IsNullOrEmpty(e.Data) == false)
{
new Thread(() =>
{
this.Dispatcher.Invoke(new Action(() =>
{
// Add you code here
}));
}).Start();
}
}
Check that the output you are expecting is not being sent to the StandardError output instead of the StandardOutput output