First of all, I'm programming in Windows Forms Application.
As the title describes, I want to perform an action when a process I started, will close.
I've tried at first:
Process s = new Process();
s.EnableRaisingEvents = true;
s = Process.Start(processToStart);
s.Exited += s_Exited;
When I do:
Process s = new Process();
s = Process.Start(processToStart);
s.EnableRaisingEvents = true;
s.Exited += s_Exited;
I get exception of System.InvalidOperationException.
Full exception details:
Additional information: Cross-thread operation not valid: Control 'Main' accessed from a thread other than the thread it was created on.
There are two problems with your original code
1- You assign a new process to s at s = Process.Start(processToStart);, so you loose s.EnableRaisingEvents = true;
2- Exited event is called from a thread different than you UI thread. Therefore you should use Invoke to avoid cross thread exception
Process p = new Process();
p.EnableRaisingEvents = true;
p.StartInfo = new ProcessStartInfo() { FileName = processToStart };
p.Exited += (s, e) =>
{
this.Invoke((Action)(() =>
{
//your code accesing UI, for ex,
this.Text = "Exited";
}));
};
p.Start();
PS: Your current code attaches to Exited event after the process has started. It may happen (with a low probability), that process exits before you attach to the event. Above code is more correct way of doint it.
Related
I was trying to wrap a EAP in a Task with following code.
public static async Task<string> Caller()
{
var ret = await RunProgram();
return ret;
}
public static async Task<string> RunProgram()
{
TaskCompletionSource<string> source = new TaskCompletionSource<string>();
var process = new Process();
process.StartInfo.UseShellExecute = true;
process.StartInfo.FileName = "cmd";
process.Exited += (sender, args) =>
{
source.SetResult("hello");
};
process.Start();
return await source.Task;
}
However,the Exited Event never gets fired. Could someone guide me on what am doing wrong here ?
Please note that above code is a prototype, the 'event-not-firing' scenario happens in the real scenario as well.
You need to enable event raising property of the Process
like this
var process = new Process
{
EnableRaisingEvents = true,
StartInfo = new ProcessStartInfo(processPath)
{
RedirectStandardError = true,
UseShellExecute = false
}
};
Without addressing any other issue.
Process.EnableRaisingEvents Property
Gets or sets whether the Exited event should be raised when the
process terminates.
Remarks
The EnableRaisingEvents property indicates whether the component
should be notified when the operating system has shut down a process.
The EnableRaisingEvents property is used in asynchronous processing to
notify your application that a process has exited. To force your
application to synchronously wait for an exit event (which interrupts
processing of the application until the exit event has occurred), use
the WaitForExit method
Example
var p = Process.Start(startInfo);
p.EnableRaisingEvents = true;
p.Exited += new EventHandler(ProcessExited);
I am redirecting output from my process (proc) to richtextbox (richTextBoxOutput). Before redirect, it write to the richtextbox sentence "BEFORE OUTPUT DATA". Then it should write all data from process and after write sentence "AFTER OUTPUT DATA". But, this last sentence is never in the end. It is always somewhere in the middle of richtextbox between redirect dates. Can you help me with a solution?
richTextBoxOutput.AppendText("BEFORE OUTPUT DATA");
Process proc = new Process();
proc.StartInfo.FileName = command;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.CreateNoWindow = true;
proc.OutputDataReceived += new DataReceivedEventHandler
(
(s, e) =>
{
if (richTextBoxOutput.InvokeRequired)
{
richTextBoxOutput.Invoke(new Action(() => richTextBoxOutput.AppendText(e.Data + "\n")));
}
else
richTextBoxOutput.AppendText(e.Data + "\n");
}
);
proc.ErrorDataReceived += new DataReceivedEventHandler((s, e) => { richTextBoxOutput.AppendText(e.Data + "\n"); });
proc.Start();
proc.BeginOutputReadLine();
while (!proc.HasExited)
{
Application.DoEvents(); //Instead of proc.WaitForExit()
}
richTextBoxOutput.AppendText("AFTER OUTPUT DATA");
Minimal working sample:
private void btnRun_Click(object sender, EventArgs e) {
Task.Factory.StartNew(this.StdOutWorker);
}
private void StdOutWorker() {
this.AppendLine("BEFORE OUTPUT DATA");
// "CmdRandomGenerator.exe" will print random numbers to standard output in infinity loop
ProcessStartInfo pi = new ProcessStartInfo("CmdRandomGenerator.exe") {
RedirectStandardOutput = true,
UseShellExecute = false
};
var proc = new Process{
StartInfo = pi,
EnableRaisingEvents = true
};
proc.Start();
while (!proc.HasExited) {
var line = proc.StandardOutput.ReadLine();
this.AppendLine(line);
}
this.AppendLine("AFTER OUTPUT DATA");
}
private void AppendLine(string line) {
Action act = () => {
this.rtbOutput.AppendText(line + Environment.NewLine);
};
// UI objects must be accessed in UI thread
this.BeginInvoke(act);
}
The idea is, the reading from process is in background thread, and the values are passed to UI thread without waiting for result (fire and forget).
Theoretically, the values from observed process may come faster than the UI can handle that. In that case you must throw away some values (sampling).
However I tried that with while(true) { Console.WriteLine(random()); } and the UI was slower but still responsive.
May I suggest a Async approach to the problem?
Yes you will have to handle cross-thread calls because you are writing to a windows form (textbox like) component from another thread, but the good part is that you can use the proc.WaitForExit without worrying about freezing the interface (without that nasty loop that is highly ineffective because it will have your app burn-out the CPU) AND have a ContinueWith clause to append your "AFTER OUTPUT DATA"!
I am trying to run several external application from inside my application. Assume that I want to run an application called LongtimeRun.exe for 10 times and each time that this applications runs, it takes around 30s to finish ( total time is 300 sec or 5 minutes!). I also want to give user some progress indication ( for example how many times the application runs).
I can create a batch file and run LongTimeRun.exe there 10 times, but then I am not able to show any progress report.
I have this code which works:
using System.Diagnostics;
using System.IO;
public class CommandProcessor
{
private readonly string binDirectory;
private readonly string workingDirectory;
public CommandProcessor(string workingDirectory, string binFolderName)
{
binDirectory = Path.Combine(FileSystem.ApplicationDirectory, binFolderName);
this.workingDirectory = workingDirectory;
}
public int RunCommand(string command, string argbase, params string[] args)
{
var commandPath = Path.Combine(binDirectory, command);
var formattedArgumets = string.Format(argbase, args);
var myProcess = new Process();
myProcess.EnableRaisingEvents = false;
myProcess.StartInfo.FileName = commandPath;
myProcess.StartInfo.Arguments = formattedArgumets;
myProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
myProcess.StartInfo.WorkingDirectory = this.workingDirectory;
myProcess.Start();
myProcess.WaitForExit();
}
}
When I calling it in tis way:
private void RunCommands()
{
var command = "LongRunCommand.exe";
string binDirectory = Path.Combine(FileSystem.ApplicationDirectory, binFolderName);
var cp = new CommandProcessor(this.workingDirectory, binDirectory);
for(int i=0;i<10;i++)
{
cp.RunCommand(Command, "-i {0}", i);
}
}
The above code is called as part of direct call and blocks the application (the applications seems to hangs during this process.
To solve the hanging problem, I used a backgroundworker as follow:
var worker = new BackgroundWorker();
worker.DoWork += this.WorkerDoWork;
worker.RunWorkerCompleted += this.workerRunWorkerCompleted;
worker.RunWorkerAsync();
and called runcommand inside WorkerDoWork.
Now the application exited after it called this line:
myProcess.WaitForExit();
There is no debug info and exit code is -1.
What is the problem and how can solve it?
Is there any better way to achieve my goal without using BackgroundWorker?
The problem you are encountering is because your BackgroundWorker threads are still running but you application completes its life-cycle and ends (it is not being blocked by them so its path is clear to end) therefore killing these threads.
You need to inform the application NOT to exit while the background threads are still running. You could have a counter that is incremented when each thread starts and then as they complete they can decrement the counter.
Inside your main application thread you could wait until the counter reaches zero before ending the application.
Obviously you will need to take into account locking (i.e. two threads try to decrement counter at the same time) but this should give you a starter.
If you want to start another process and wait (with time out) to finish you can use the following (from MSDN).
//Set a time-out value.
int timeOut=5000;
//Get path to system folder.
string sysFolder=
Environment.GetFolderPath(Environment.SpecialFolder.System);
//Create a new process info structure.
ProcessStartInfo pInfo = new ProcessStartInfo();
//Set file name to open.
pInfo.FileName = sysFolder + #"\eula.txt";
//Start the process.
Process p = Process.Start(pInfo);
//Wait for window to finish loading.
p.WaitForInputIdle();
//Wait for the process to exit or time out.
p.WaitForExit(timeOut);
//Check to see if the process is still running.
if (p.HasExited == false)
//Process is still running.
//Test to see if the process is hung up.
if (p.Responding)
//Process was responding; close the main window.
p.CloseMainWindow();
else
//Process was not responding; force the process to close.
p.Kill();
MessageBox.Show("Code continuing...");
If you want to start another process and read its output then you can use the following pattern (from SO)
// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "Write500Lines.exe";
p.Start();
// Do not wait for the child process to exit before
// reading to the end of its redirected stream.
// p.WaitForExit();
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
How can you combine the two to read all input, not get stuck in deadlock and have a timeout if the running process goes awry?
This technique will hang if the output buffer is filled with more that 4KB of data. A more foolproof method is to register delegates to be notified when something is written to the output stream. I've already suggested this method before in another post:
ProcessStartInfo processInfo = new ProcessStartInfo("Write500Lines.exe");
processInfo.ErrorDialog = false;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardOutput = true;
processInfo.RedirectStandardError = true;
Process proc = Process.Start(processInfo);
// You can pass any delegate that matches the appropriate
// signature to ErrorDataReceived and OutputDataReceived
proc.ErrorDataReceived += (sender, errorLine) => { if (errorLine.Data != null) Trace.WriteLine(errorLine.Data); };
proc.OutputDataReceived += (sender, outputLine) => { if (outputLine.Data != null) Trace.WriteLine(outputLine.Data); };
proc.BeginErrorReadLine();
proc.BeginOutputReadLine();
proc.WaitForExit();
You don't have to combine the two - the Process class has an event that fires when output is sent to the StandardOutput - OutputDataReceived.
If you subscribe to the event, you will be able to read output as it arrives and in your main program loop you can still timeout.
you can try modifying the first method to something like this
Process p = Process.Start(pInfo);
string output = string.Empty;
Thread t = new Thread(() => output = p.StandardOutput.ReadToEnd() );
t.Start();
//Wait for window to finish loading.
p.WaitForInputIdle();
//Wait for the process to exit or time out.
p.WaitForExit(timeOut);
void OpenWithStartInfo()
{
ProcessStartInfo startInfo = new ProcessStartInfo("IExplore.exe", "Default2.aspx");
startInfo.WindowStyle = ProcessWindowStyle.Minimized;
Process p = Process.Start(startInfo);
p.WaitForInputIdle();
//p.WaitForExit(2);
p.Kill();
}
You could also use the APM, like this:
Define a delegate for the ReadToEnd call:
private delegate string ReadToEndDelegate();
Then use the delegate to call the method like this:
ReadToEndDelegate asyncCall = reader.ReadToEnd;
IAsyncResult asyncResult = asyncCall.BeginInvoke(null, null);
asyncResult.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(10));
asyncCall.EndInvoke(asyncResult);
EDIT: Error handling removed for clarity.
Just add everything from the first example below the WaitForExit() call to the second example.
None of the above answers work for me when dealing with interactive promts. (My command sometimes promts a question to the user and that should also be covered by timeout).
This is my solution.
A disadvantage is that i don't get any output if we run in a timeout.
ReadToEnd() blocks the execution so we have to run it in another thread and kill this thread if the process runs into the specified timeout.
public static Tuple<string, string> ExecuteCommand(string command)
{
// prepare start info
var procStartInfo = new ProcessStartInfo("cmd", "/c " + command)
{
ErrorDialog = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
WorkingDirectory = #"C:\",
CreateNoWindow = true
};
// start process
var proc = new Process {StartInfo = procStartInfo};
proc.Start();
var error = "";
var output = "";
// read stdout and stderr in new thread because it is blocking
Thread readerThread = new(() =>
{
try
{
error = proc.StandardError.ReadToEnd().Trim();
output = proc.StandardOutput.ReadToEnd().Trim();
}
catch (ThreadInterruptedException e)
{
Debug.WriteLine("Interrupted!!");
}
});
readerThread.Start();
// wait for max 6 seconds
if (proc.WaitForExit(6_000))
{
// if command runs to an enc => wait for readerThread to collect error/output stream
readerThread.Join();
}
else
{
// if process takes longer than 6 seconds => kill reader thread and set error to timeout
readerThread.Interrupt();
error = "Timeout!";
}
// return output and error
return new Tuple<string, string>(output, error);
}
I am new to the thread model in .NET. What would you use to:
Start a process that handles a file (process.StartInfo.FileName = fileName;).
Wait for the user to close the process OR abandon the thread after some time.
If the user closed the process, delete the file.
Starting the process and waiting should be done on a different thread than the main thread, because this operation should not affect the application.
Example:
My application produces an html report. The user can right click somewhere and say "View Report" - now I retrieve the report contents in a temporary file and launch the process that handles html files i.e. the default browser. The problem is that I cannot cleanup, i.e. delete the temp file.
"and waiting must be async" - I'm not trying to be funny, but isn't that a contradiction in terms? However, since you are starting a Process, the Exited event may help:
ProcessStartInfo startInfo = null;
Process process = Process.Start(startInfo);
process.EnableRaisingEvents = true;
process.Exited += delegate {/* clean up*/};
If you want to actually wait (timeout etc), then:
if(process.WaitForExit(timeout)) {
// user exited
} else {
// timeout (perhaps process.Kill();)
}
For waiting async, perhaps just use a different thread?
ThreadPool.QueueUserWorkItem(delegate {
Process process = Process.Start(startInfo);
if(process.WaitForExit(timeout)) {
// user exited
} else {
// timeout
}
});
Adding an advanced alternative to this old question. If you want to wait for a process to exit without blocking any thread and still support timeouts, try the following:
public static Task<bool> WaitForExitAsync(this Process process, TimeSpan timeout)
{
ManualResetEvent processWaitObject = new ManualResetEvent(false);
processWaitObject.SafeWaitHandle = new SafeWaitHandle(process.Handle, false);
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
RegisteredWaitHandle registeredProcessWaitHandle = null;
registeredProcessWaitHandle = ThreadPool.RegisterWaitForSingleObject(
processWaitObject,
delegate(object state, bool timedOut)
{
if (!timedOut)
{
registeredProcessWaitHandle.Unregister(null);
}
processWaitObject.Dispose();
tcs.SetResult(!timedOut);
},
null /* state */,
timeout,
true /* executeOnlyOnce */);
return tcs.Task;
}
Again, the advantage to this approach compared to the accepted answer is that you're not blocking any threads, which reduces the overhead of your app.
Try the following code.
public void KickOffProcess(string filePath) {
var proc = Process.Start(filePath);
ThreadPool.QueueUserWorkItem(new WaitCallBack(WaitForProc), proc);
}
private void WaitForProc(object obj) {
var proc = (Process)obj;
proc.WaitForExit();
// Do the file deletion here
}
The .NET 5 introduced the new API Process.WaitForExitAsync, that allows to wait asynchronously for the completion of a process. It offers the same functionality with the existing Process.WaitForExit, with the only difference being that the waiting is asynchronous, so it does not block the calling thread.
Usage example:
private async void button1_Click(object sender, EventArgs e)
{
string filePath = Path.Combine
(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
Guid.NewGuid().ToString() + ".txt"
);
File.WriteAllText(filePath, "Hello World!");
try
{
using Process process = new();
process.StartInfo.FileName = "Notepad.exe";
process.StartInfo.Arguments = filePath;
process.Start();
await process.WaitForExitAsync();
}
finally
{
File.Delete(filePath);
}
MessageBox.Show("Done!");
}
In the above example the UI remains responsive while the user interacts with the opened file. The UI thread would be blocked if the WaitForExit had been used instead.
I would probably not use a separate process for opening a file. Instead, I'd probably utilize a background thread (if I thought the operation was going to take a long time and possible block the UI thread).
private delegate void FileOpenDelegate(string filename);
public void OpenFile(string filename)
{
FileOpenDelegate fileOpenDelegate = OpenFileAsync;
AsyncCallback callback = AsyncCompleteMethod;
fileOpenDelegate.BeginInvoke(filename, callback, state);
}
private void OpenFileAsync(string filename)
{
// file opening code here, and then do whatever with the file
}
Of course, this is not a good working example (it returns nothing) and I haven't shown how the UI gets updated (you have to use BeginInvoke at the UI level because a background thread cannot update the UI thread). But this approach is generally how I go about handling asynchronous operations in .Net.
You can use the Exited event in Process class
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "notepad.exe";
Process process = Process.Start(info);
process.Exited += new EventHandler(process_Exited);
Console.Read();
and in that event you can handle the operations you mentioned