Updating a Label with the backgroundworker progress - c#

I am reading line by line console of an external exe with the help of a backgroundworker, i am assigning each line of console to a label. the problem is the label is not updating with the console line. code is given below
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
int i = 0;
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
startInfo.FileName = EXELOCATION;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.Arguments = Program.path;
startInfo.RedirectStandardOutput = true;
try
{
// Start the process with the info we specified.
// Call WaitForExit and then the using statement will close.
using (exeProcess = Process.Start(startInfo))
{
using (StreamReader reader = exeProcess.StandardOutput)
{
string result;
while ((result = reader.ReadLine()) != null)
{
// object param = result;
e.Result = result;
bgWorker.ReportProgress(i++);
}
}
}
}
catch
{
// Log error.
}
}
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label.Text = e.ToString();
label.Refresh();
}
how can i solve this issue

try this:
label2.Invoke(new Action(() => { label2.Text = e.ToString(); }));
label2.Invoke(new Action(() => { label2.Refresh(); }));

That code probably doesn't work because you're trying to update an UI element from a non-UI thread (aka background thread).
If you're using WPF, you should use the Dispatcher to request that the label be changed in the UI thread. If you're using another framework, try that framework's equivalent class.
In your ProgressChanged method, try this instead:
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
() => {
label.Text = e.ToString();
});

If this is in another thread (and you are in a winforms application), you might need to use the Control.InvokeRequired
public void UpdateProgress (int progress)
{
if (label.InvokeRequired)
{
this.Invoke(()=> UpdateProgress(progress));
}
else
{
label.Text = progress.ToString();
}
}
This method checks whether it's run on the UI thread, and if it is not, it calls itself on the UI thread. If it is already on the UI thread, it simply updates the label.

Related

How to show Progressbar untill a process ends

I'm having a problem when I' trying to show a progress bar until a external process ends which I started for the WPF window.
The code is like this:
private void Button1_Click(object sender, RoutedEventArgs e)
{
Button1.IsEnabled = false;
Button1.Content = "Please Wait";
ProgressBar1.Visibility = Visibility.Visible;
if (a == 1 && b == 0)
{
var processStartInfo = new ProcessStartInfo(#"External Process Path 1");
processStartInfo.Verb = "runas";
try
{
Process.Start(processStartInfo);
}
catch (Win32Exception ex)
{
MessageBox.Show(ex.ToString(), "Run As",
MessageBoxButton.OK, MessageBoxImage.Exclamation);
}
}
if (b == 1 && a == 0)
{
var processStartInfo = new ProcessStartInfo(#"External Process Patch 2");
processStartInfo.Verb = "runas";
try
{
Process.Start(processStartInfo);
}
catch (Win32Exception ex)
{
MessageBox.Show(ex.ToString(), "Run As",
MessageBoxButton.OK, MessageBoxImage.Exclamation);
}
}
Button2.IsEnabled = true;
ProgressBar1.Visibility = Visibility.Hidden; //This is what I want to toggle after process ends
}
I've tried Thread.Sleep(time) method and also for-loop but nothing seems to work.
I'm very new in WPF. So, please try to be a little brief.
Thanks,
D.K.
do you know how long the external process lasts? if you don't you can try to set the IsIndeterminate property to true on your progress bar. this will show a continuous animation. When your process returns you can set it to false again in order to stop the animation.
also, in your code, i think you're not waiting for the process to finish. you could do this by using the code below.
Process p = Process.Start("IExplore");
p.WaitForExit();
please note that WaitForExit() blocks the current thread. as a result the app will stop responding. to keep the UI responsive you might like to start your process on a different thread like below.
private void onClick_Handler(object sender, EventArgs e) {
//disable button here
Task.Factory.StartNew(() => {
Process p = Process.Start("IExplore");
p.WaitForExit();
//enable button here. make sure to do this on the UI thread
//since you're doing this in the code-behind you should have access
//to the dispatcher
Dispatcher.BeginInvoke((Action)OnUpdateUI);
});
}
private void OnUpdateUI(){
}
In the above code, you are starting a process but not waiting for it to end, so the debugger when executes your:
Process.Star("Add Some Task");
, it jumps on to the next statement which is
button2.IsEnabled = true;
and so on. As a result your ProgressBar1 is not visible to you.
Please wait for the Process to end first. Write
Process.WaitForExit();
just after your statement
Process.Start("Your already started task");
Also you can create an Asynchronous thread to run in parallel.
Example:
Task taskA = new Task( () => Console.WriteLine("Hello from taskA."));
taskA.Start();
taskA.Wait();
Also in your above code, you are only displaying ProgressBar but not updating its value with time. So as a result ProgressBar will be visible only with initial value.
For ProgressBar, do something like,
ProgressBar ProgressBar1 = new ProgressBar();
ProgressBar1.Maximum = 100;
ProgressBar1.Minimum = 0;
Task.Start();
if( Task.Status == "Running")
{
ProgressBar1.Value = 50;
}
if( Task.Status == "completed")
{
ProgressBar1.Value =100;
}
else
{
ProgressBar.Value=0;
Task.Wait();
}
The code above mentioned may not be syntactically right. So do look for correct syntax.

Accesing two threads in one button click function

This is not throwing me any error but after executing first thread it is not executing second thread. Am I doing anything wrong?
Below is my code:
My button click Function:
private void ImportBothButtonclick(object sender, EventArgs e)
{
// Get the currently selected manufacturer from the combo box
var selected = comboBox.SelectedItem;
// Do we have one?
if (selected != null)
{
// Extract the combo record
var val= (ComboBoxItem)selected;
// Do we have one?
if (val != null)
{
// yes
// Make this on a seperate thread so that the UI continues to work
Invoke(new System.Action(() =>
{
button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = false;
var thread = new Thread(DoFunction1);
thread.Start(val);
}));
Invoke(new System.Action(() =>
{
button1.Enabled = false;
button2.Enabled = false;
button3Enabled = false;
var thread = new Thread(DoFunction2);
thread.Start(val);
}));
}
}
}
Those Actions will not do a thing. The actions are Invoked on the same thread you're currently on.
The threads are currently running parallel. If you want those threads to run in serial but not on the gui thread, you could do something like this:
This is the NON-TASK version.
// not allowed on a non-gui thread.
button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = false;
new Thread(() =>
{
DoFunction1();
DoFunction2();
// execute this on the gui thread. (winforms)
this.Invoke(new Action( delegate
{
button1.Enabled = true;
button2.Enabled = true;
button3.Enabled = true;
}));
}).Start();
If you want to run them parallel, but wait until they are done:
// not allowed on a non-gui thread.
button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = false;
new Thread(() =>
{
ManualResetEvent wait1 = new ManualResetEvent(false);
ManualResetEvent wait2 = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem((state) =>
{
DoFunction1();
wait1.Set();
});
ThreadPool.QueueUserWorkItem((state) =>
{
DoFunction2();
wait2.Set();
});
ManualResetEvent.WaitAll(new WaitHandle[] { wait1, wait2 });
this.Invoke(new Action( delegate
{
// execute this on the gui thread. (winforms)
button1.Enabled = true;
button2.Enabled = true;
button3.Enabled = true;
}));
}).Start();
But this can be more easy using tasks. Task Parallelism (Task Parallel Library) http://msdn.microsoft.com/en-us/library/dd537609.aspx
Please clarify what is the exact problem you observe?
According to what you have currently stated, the problem lies in "the second thread is not run after the first thread".
So, let me answer to this.
Your code is almost OK. You have overlooked one important thing: your code "new thread / thread.start()" actually do start a new thread, and then it does not wait for that thread to execute nor complete.
lines:
new thread(f1).Start()
new thread(f2).Start()
will not "run F1 on thread1, then run F2 on thread2". Instead, they will "begin to run F1 on thread1 and immediately begin to run F2 on thread2".
To have the F2 executed only after F1 has fully finished, you must somehow 'chain' the two together:
you can create simple "agenda" method and run it instead:
private void doAllTasks()
{
f1();
f2();
}
new thread(doAllTasks).Start()
you can try "chaining" them on the fly via lambdas, which effectively is the same as above:
new thread(() => { f1(); f2(); ).Start()
you can actually run them both immediatelly, but have the F2 join [wait] until the F1's thread ends
var th1 = new thread(f1);
var th2 = new thread(() => {th1.Join(); f2();} )
th1.Start();
th2.Start();
// note that this code is NOT perfect, there's some error handling to do here, etc..
or, you can try some nice and pretty wrappers for all of that like the TPL framework, as seen in Sheridan's answer.
And of course you must be careful with touching UI elements from inside what those other new thread's are running. Sheridan's answer already covers it via TPL ways. Manually, you'd have to use Invoke/BeginInvoke to bounce the UI-related code back to UI thread. In your current code you already have it, but at that place, it is not necessary, since the _Click handler method obviously already runs in UI thread.
Therefore, your current example can be reduced to i.e.:
private void ImportBothButtonclick(object sender, EventArgs e)
{
var selected = comboBox.SelectedItem;
if (selected != null)
{
var val= (ComboBoxItem)selected;
if (val != null)
{
// no Invoke needed, "button_click" handlers
// are already on UI thread, so touching UI things is OK
button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = false;
// starting a new thread also does not need Invoke
var thread = new Thread(DoAllFunctions);
thread.Start(val);
}
}
}
private void DoAllFunctions(object something)
{
DoFunction1(something);
DoFunction2(something);
// button1.Enabled = true; - cannot do it here because they are UI
// button2.Enabled = true; - and DoAll is run from other thread.
// button3.Enabled = true; - you must bounce that back to UI thread.
LetTheUIKnowJobsAreFinished(); // <- performed here
}
private void LetTheUIKnowJobsAreFinished()
{
Invoke(new Action(()=>{
button1.Enabled = true;
button2.Enabled = true;
button3.Enabled = true;
});
}
Also, as a final note, please go and look at the BackgroundWorker from System.ComponentModel. It has very nice set of events/callbacks that will make all the thread-crossing very easy.
(btw. let me say that again: this code is NOT usable. it is only a sketch. you will find typos, missing colons, missing try-catch-whatever etc!)
Maybe you could do something like this?:
Task.Factory.StartNew(new Action(() =>
{
button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = false;
}), new CancellationToken(), TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext()).
ContinueWith(new Action(() => DoFunction1)).
ContinueWith(new Action(() =>
{
button1.Enabled = false;
button2.Enabled = false;
button3Enabled = false;
}), TaskScheduler.FromCurrentSynchronizationContext()).
ContinueWith(new Action(() => DoFunction2));

Dispatcher timer not running

I'm trying to execute some Python scripts from my WPF app. The scripts are generating the log files and the code in the Tick event is reading them and displaying that as it is in a textbox.
My issue here is that, that LaunchProcess fires successfully, but the UI freezes. I have an indefinite progress bar, which too does not start animating. I'm a beginner with WPF and there is something very small I have to do to get this code working. I'm not getting any error/warnings. The scripts run fine and in the end I get to know the result too. But during the run, the UI of my app freezes.
private void LaunchProcess(string paramStr)
{
Process myProcess = new Process();
StartProgressBar();
try
{
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0, 0, 0);
dispatcherTimer.Start();
myProcess.StartInfo.UseShellExecute = false;
// You can start any process
myProcess.StartInfo.FileName = "C:\\Python32\\python.exe";
myProcess.StartInfo.Arguments = "\""+paramStr+"\"";
myProcess.StartInfo.CreateNoWindow = true;
myProcess.StartInfo.RedirectStandardOutput = true;
myProcess.StartInfo.RedirectStandardError = true;
myProcess.Start();
myProcess.WaitForExit();
// This code assumes the process you are starting will terminate itself.
// Given that is is started without a window so you cannot terminate it
// on the desktop, it must terminate itself or you can do it programmatically
// from this application using the Kill method.
dispatcherTimer.Stop();
}
catch
{
MessageBox.Show("Process Launch Failed!!", "Failure", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
//txtOutPut.Text = "";
txtOutPut.Text += "\n" + DateTime.Now.ToString();
if (File.Exists(scriptPath+"\\log.txt"))
{
//File.Copy("C:\\FlashAuto\\Execution_Logs\\log.txt", "C:\\FlashAuto\\Temp\\log.txt", true);
TextReader readLogs = new StreamReader(scriptPath + "\\log.txt");
string line = readLogs.ReadLine();
while (line != null)
{
txtOutPut.Text += "\n" + line;
line = readLogs.ReadLine();
txtOutPut.ScrollToEnd();
}
//CountLines = txtExecLog.LineCount - 1;
readLogs.Close();
// Forcing the CommandManager to raise the RequerySuggested event
txtOutPut.ScrollToEnd();
CommandManager.InvalidateRequerySuggested();
readLogs.Dispose();
}
else
{
txtOutPut.Text += "log file not found at: " + DateTime.Now.ToString();
}
}
In case you call LaunchProcess from the UI thread it will obviously be blocked at myProcess.WaitForExit().
You might simply remove the myProcess.WaitForExit() and dispatcherTimer.Stop() calls from the launch method and check if the process is still running in the timer Tick handler.
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
if (myProcess.WaitForExit(0)) // check with timeout zero
{
dispatcherTimer.Stop();
}
... // all your code
}
Calling LaunchProcess method asynchronously would resolve your UI Freeze Issue
public void LaunchProcessAsynchrousCall(string paramStr)
{
ThreadStart displayContentHandler = delegate()
{
LaunchProcess(paramStr)
};
Thread thread = new Thread(displayContentHandler);
thread.IsBackground = true;
thread.Start();
}

How can I redirect process output (console) to richtextbox?

what is wrong why is that the richtextbox doesnt get the stream of Process output? theres no text display in richtextbox..
private void button1_Click(object sender, EventArgs e)
{
Process sortProcess;
sortProcess = new Process();
sortProcess.StartInfo.FileName = "sort.exe";
sortProcess.StartInfo.Arguments = this.comboBox1.SelectedItem.ToString();
// Set UseShellExecute to false for redirection.
sortProcess.StartInfo.CreateNoWindow = true;
sortProcess.StartInfo.UseShellExecute = false;
// Redirect the standard output of the sort command.
// This stream is read asynchronously using an event handler.
sortProcess.StartInfo.RedirectStandardOutput = true;
sortOutput = new StringBuilder("");
// Set our event handler to asynchronously read the sort output.
sortProcess.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
// Redirect standard input as well. This stream
// is used synchronously.
sortProcess.StartInfo.RedirectStandardInput = true;
// Start the process.
sortProcess.Start();
// Start the asynchronous read of the sort output stream.
sortProcess.BeginOutputReadLine();
sortProcess.WaitForExit();
richTextBox1.AppendText(sortOutput.ToString());
}
private static void SortOutputHandler(object sendingProcess,
DataReceivedEventArgs outLine)
{
sortOutput.Append(Environment.NewLine +
"[" + numOutputLines.ToString() + "] - " + outLine.Data);
}
}
so when sort.exe launches, it displays text, i want all those text be displayed also in richtextbox in RealTime (i dont want to wait for the process to exit, and then read all output)
how can i do it? any wrong part of my code? thanks
UPDATE #botz
i added this in my code
private void SortOutputHandler(object sendingProcess,
DataReceivedEventArgs outLine)
{
sortOutput.Append(Environment.NewLine +
"[" + numOutputLines.ToString() + "] - " + outLine.Data);
richTextBox1.AppendText(sortOutput.ToString());
}
but it throws this exception
Cross-thread operation not valid: Control 'richTextBox1' accessed from a thread other than the thread it was created on.
WaitForExit() blocks your UI Thread, so you don't see the new output.
Either wait for the process in a separate thread or replace WaitForExit() with something like this:
while (!sortProcess.HasExited) {
Application.DoEvents(); // This keeps your form responsive by processing events
}
In your SortOutputHandler, you can now directly append output to your textbox. But you should remember to check if you need to invoke it on the UI Thread.
You can check if it's on the UI thread this way in your handler:
if (richTextBox1.InvokeRequired) { richTextBox1.BeginInvoke(new DataReceivedEventHandler(SortOutputHandler), new[] { sendingProcess, outLine }); }
else {
sortOutput.Append(Environment.NewLine + "[" + numOutputLines.ToString() + "] - " + outLine.Data);
richTextBox1.AppendText(sortOutput.ToString());
}
This is working for me:
private void button1_Click(object sender, EventArgs e)
{
using (Process sortProcess = new Process())
{
sortProcess.StartInfo.FileName = #"F:\echo_hello.bat";
sortProcess.StartInfo.CreateNoWindow = true;
sortProcess.StartInfo.UseShellExecute = false;
sortProcess.StartInfo.RedirectStandardOutput = true;
// Set event handler
sortProcess.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
// Start the process.
sortProcess.Start();
// Start the asynchronous read
sortProcess.BeginOutputReadLine();
sortProcess.WaitForExit();
}
}
void SortOutputHandler(object sender, DataReceivedEventArgs e)
{
Trace.WriteLine(e.Data);
this.BeginInvoke(new MethodInvoker(() =>
{
richTextBox1.AppendText(e.Data ?? string.Empty);
}));
}
The example you started with was a console application, which doesn't care much about multithreaded access. For Windows Forms when you update a control this has to be done from the main UI thread, which is why BeginInvoke is needed. If you want to check rapidly if a handler like SortOutputHandler is working properly you can use System.Diagnostics.Trace.Write*, which doesn't need BeginInvoke.
EDIT: echo_hello.bat simply echoes the "hello" string:
#echo off
echo hello
If you are going to update the ui from another thread, you need to make sure you are on the main ui thread. In the method check for InvokeRequired. See InvokeRequired
Complete application and source code available from this external link of codeproject :
http://www.codeproject.com/Articles/335909/Embedding-a-Console-in-a-C-Application
This is tutorial of implementation of https://github.com/dwmkerr/consolecontrol.
As I said in the comment I posted to the question, by definition of what a sort does, it is impossible for there to be any output until all the input has been read. So the sort program is a bad example of getting output in realtime. So the following is for anyone in the future that wants to do something like this for console programs in general. The following uses a BackgroundWorker to get the output asynchronously and put it into a TextBox. A RichTextBox could easily be used instead.
public partial class MainWindow : Window
{
const string Path = #"C:\Windows\system32\sort.exe";
BackgroundWorker Processer = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
Processer.WorkerReportsProgress = true;
Processer.WorkerSupportsCancellation = true;
Processer.ProgressChanged += Processer_ProgressChanged;
Processer.DoWork += Processer_DoWork;
}
private void Processer_DoWork(object sender, DoWorkEventArgs e)
{
StreamReader StandardOutput = e.Argument as StreamReader;
string data = StandardOutput.ReadLine();
while (data != null)
{
Processer.ReportProgress(0, data);
data = StandardOutput.ReadLine();
}
}
private void Processer_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
string data = e.UserState as string;
if (data != null)
DataBox.Text += data + "\r\n";
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DataBox.Text = string.Empty;
ProcessStartInfo StartInfo = new ProcessStartInfo(Path);
StartInfo.RedirectStandardOutput = true;
StartInfo.RedirectStandardInput = true;
StartInfo.UseShellExecute = false;
Process p = null;
try { p = Process.Start(StartInfo); }
catch (Exception ex)
{
MessageBox.Show($"Error starting {Path}: {ex.Message}");
return;
}
// Get the output
Processer.RunWorkerAsync(p.StandardOutput);
// Put the input
p.StandardInput.WriteLine("John");
p.StandardInput.WriteLine("Alice");
p.StandardInput.WriteLine("Zoe");
p.StandardInput.WriteLine("Bob");
p.StandardInput.WriteLine("Mary");
// Tell the program that is the last of the data
p.StandardInput.Close();
}
}
For the sort program it is not necessary to call ReportProgress until after all the data has been read but this is a more generalized sample.

What to cast a Process class (sender) thread to....in event handler

For instance, a thread that is a BackgroundWorker, can be cast like:
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
System.ComponentModel.BackgroundWorker senderWorker
= sender as System.ComponentModel.BackgroundWorker;
}
The code above represents what I have for my Background worker thread. I cast [sender] as a BackGround Worker - because I know thats what he is.
I can't seem to find what I should cast it to if: instead of a Background worker, what if I had used a Process class, and executed say a DOS batch file, using:
enter code here
Process proc = new Process();
proc.FileName = "some_dos_batch_file.bat";
proc.Exited = ProcessExited;
proc.Start();
Sorry about syntax, but when this process completes, its completion will be handled by 'ProcessExited' below. But What should I cast the sender arg to in THAT case - NOT a Background Worker obviously, but I'm not sure to what? I would like to use the .Results property the same as I did for the Background worker.
Thanks - sorry for the confusion.
enter code here
void ProcessExited(object sender, EventArgs e)
{
}
I hope I have understood your question, if not, please clarify. If you are talking about threading and using the System.Diagnostics.Process then you would need to use Thread events...consider this below a simple class called TestARP that shells out to the command line using a hidden window to retrieve the MAC/IP address of the active connection, with the output of the command redirected to a stream which is appended to a stringbuilder instance:
public class TestARP
{
private StringBuilder sbRedirectedOutput = new StringBuilder();
public string OutputData
{
get { return this.sbRedirectedOutput.ToString(); }
}
public void Run()
{
System.Diagnostics.ProcessStartInfo ps = new System.Diagnostics.ProcessStartInfo();
ps.FileName = "arp";
ps.ErrorDialog = false;
ps.Arguments = "-a";
ps.CreateNoWindow = true;
ps.UseShellExecute = false;
ps.RedirectStandardOutput = true;
ps.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
using (System.Diagnostics.Process proc = new System.Diagnostics.Process())
{
proc.StartInfo = ps;
proc.Exited += new EventHandler(proc_Exited);
proc.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(proc_OutputDataReceived);
proc.Start();
proc.WaitForExit();
proc.BeginOutputReadLine();
while (!proc.HasExited) ;
}
}
void proc_Exited(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("proc_Exited: Process Ended");
}
void proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
if (e.Data != null) this.sbRedirectedOutput.Append(e.Data + Environment.NewLine);
//System.Diagnostics.Debug.WriteLine("proc_OutputDataReceived: Data: " + e.Data);
}
}
If you were to run this in a thread the Process's events will still get caught (only on the thread itself), but if you're talking about waiting for the thread to finish, look at this class code here called ThreadTestARP that runs the above class on a thread...
public class ThreadTestARP
{
private TestARP _testARP = new TestARP();
private ManualResetEvent _mre = new ManualResetEvent(false);
public ThreadTestARP()
{
}
public TestARP ARPTest
{
get { return this._testARP; }
}
public void Run()
{
Thread t = new Thread(new ThreadStart(RunThread));
t.Start();
this._mre.WaitOne();
// Blocks here...
t.Join();
}
private void RunThread()
{
this._testARP.Run();
this._mre.Set();
}
}
Note how the ManualResetEvent _mre is used to signal to say in the context of the thread, "right, I am done, back to the creator..."
Why can't you cast to a Process object? You can still access some members of Process objects, such as ExitCode or ExitTime, that have terminated.
http://msdn.microsoft.com/en-us/library/system.diagnostics.process.exited.aspx

Categories

Resources