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);
}
Related
I have this task in C# that should return the standard output of DISM, so I can use it where i need:
public async Task<StreamReader> DISM(string Args)
{
StreamReader DISMstdout = null;
await Task.Run(() =>
{
Process DISMcmd = new Process();
if (Environment.Is64BitOperatingSystem)
{
DISMcmd.StartInfo.FileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "SysWOW64", "dism.exe");
}
else
{
DISMcmd.StartInfo.FileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "System32", "dism.exe");
}
DISMcmd.StartInfo.Verb = "runas";
DISMcmd.StartInfo.Arguments = DISMArguments;
DISMcmd.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
DISMcmd.StartInfo.CreateNoWindow = true;
DISMcmd.StartInfo.UseShellExecute = false;
DISMcmd.StartInfo.RedirectStandardOutput = true;
DISMcmd.EnableRaisingEvents = true;
DISMcmd.Start();
DISMstdout = DISMcmd.StandardOutput;
DISMcmd.WaitForExit();
});
return DISMstdout;
}
But it doesn't really work.
If I want to read the standardoutput from another task I can't (because it is empty) So there must be a problem with my task?.
public async Task Test()
{
await Task.Run(() =>
{
StreamReader DISM = await new DISM("/Get-ImageInfo /ImageFile:" + ImagePath + #" /Index:1");
string data = string.Empty;
MessageBox.Show(DISM.ReadToEnd()); // this should display a msgbox with the standardoutput of dism
while ((data = DISM.ReadLine()) != null)
{
if (data.Contains("Version : "))
{
// do something
}
}
});
}
What is wrong with this piece of code?
The way I'd write your method to exploit async..await as opposed to the legacy asynchronous approaches is like this:
public async Task<TResult> WithDism<TResult>(string args, Func<StreamReader, Task<TResult>> func)
{
return await Task.Run(async () =>
{
var proc = new Process();
var windowsDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
var systemDir = Environment.Is64BitOperatingSystem ? "SysWOW64" : "System32";
proc.StartInfo.FileName = Path.Combine(windowsDir, systemDir, "dism.exe");
proc.StartInfo.Verb = "runas";
proc.StartInfo.Arguments = args;
proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.Start();
Console.Error.WriteLine("dism started");
var result = await func(proc.StandardOutput);
Console.Error.WriteLine("func finished");
// discard rest of stdout
await proc.StandardOutput.ReadToEndAsync();
proc.WaitForExit();
return result;
});
}
Since realistically, the only part where significant blocking can occur when spawning a process is as you handle the output it produces. Used like this:
var task = WithDism("/?", async sr => await sr.ReadToEndAsync()); // or process line-by-line
Console.WriteLine("dism task running");
Console.WriteLine(await task);
it produces the following output
dism task running
dism started
func finished
Error: 740
Elevated permissions are required to run DISM.
Use an elevated command prompt to complete these tasks.
Do note that when using subprocesses, it's your job to make sure they correctly exit or are shut down to avoid leaving zombie processes around. That's why I've added the possibly redundant ReadToEndAsync() - in case func still leaves some output unconsumed, this should allow the process to reach its natural end.
However, this means the calling function will only proceed once that happens. If you leave behind a lot of unconsumed output you're not interested in, this will cause an unwanted delay. You could work around this by spawning off this cleanup to a different background task and returning the result immediately using something like:
Task.Run(() => {
// discard rest of stdout and clean up process:
await proc.StandardOutput.ReadToEndAsync();
proc.WaitForExit();
});
but I admit I'm going a bit out on a limb there, I'm not entirely sure about the robustness of just letting a task "run wild" like that. What the appropriate way to clean up the process is will, of course, depend on what it's actually doing after you get the output you want to return from func.
I'm using synchronous calls to Console there because they only serve to illustrate the timing of events, I want to know that as execution reaches that point. Normally you would use async in a "viral" way to make sure control passes back to top-level as soon as possible.
After playing around with this using Benchmark.NET, it seems that starting a process (I tried DISM and Atom to have something hefty) - from setup to Start() - takes about 50 milliseconds. This seems pretty negligible to me for this use. After all, 50ms is good enough latency for say playing League of Legends, and you're not going to start these in a tight loop.
I'd like to provide an alternative answer of "don't bother with Task.Run() and just use async I/O in a straightforward way" unless you absolutely need to get rid of that delay and believe spawning off a background thread will help:
static string GetDismPath()
{
var windowsDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
var systemDir = Environment.Is64BitOperatingSystem ? "SysWOW64" : "System32";
var dismExePath = Path.Combine(windowsDir, systemDir, "dism.exe");
return dismExePath;
}
static Process StartDism(string args)
{
var proc = new Process
{
StartInfo =
{
FileName = GetDismPath(),
Verb = "runas",
Arguments = args,
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true
}
};
proc.Start();
return proc;
}
static void Cleanup(Process proc)
{
Task.Run(async () =>
{
proc.StandardInput.Close();
var buf = new char[0x1000];
while (await proc.StandardOutput.ReadBlockAsync(buf, 0, buf.Length).ConfigureAwait(false) != 0) { }
while (await proc.StandardError.ReadBlockAsync(buf, 0, buf.Length).ConfigureAwait(false) != 0) { }
if (!proc.WaitForExit(5000))
{
proc.Kill();
}
proc.Dispose();
});
}
static async Task Main(string[] args)
{
var dismProc = StartDism("/?");
// do what you want with the output
var dismOutput = await dismProc.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
await Console.Out.WriteAsync(dismOutput).ConfigureAwait(false);
Cleanup(dismProc);
}
I'm only using Task.Run() to keep the cleanup off the main thread in case you need to do something else while DISM keeps producing output you're not interested in that you do not wish to kill outright.
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've a method that contains a process that must be stopped in a deadline( ex: 3 seconds) whether it has finished or not, and I don't want to wait if it has finished executing before reaching that dead line.
using Process.WaitForExit(3000) makes the program wait 3s even if the process has stopped before reaching the limit.
One more thing, I'm using process.StandardOutput.ReadToEnd(); to read the execution result, I don't care if it returns null or empty string or whatever if it doesn't finish.
And I guess that timers will cause the same problem.
Any Ideas?
Exited event of your process can be handled for detecting exit time.
WaitForExit returns a Boolean value that indicates your process has reached the timeout before exit or not.
Test this code:
Process proc = new Process();
ProcessStartInfo procInfo = new ProcessStartInfo()
{
FileName = "d:/test.exe",
UseShellExecute = false,
RedirectStandardOutput = true
};
proc.StartInfo = procInfo;
proc.EnableRaisingEvents = true;
proc.Exited += (o, args) =>
{
MessageBox.Show(proc.StandardOutput.ReadToEnd());
};
proc.Start();
if (proc.WaitForExit(3000))
{
MessageBox.Show("YES");
}
else
{
MessageBox.Show("NO");
}
I use ProcessStartInfo to run a console aplication and ProcessStartInfo can read text from the console after the console is closed:
using (Process p = Process.Start(st))
{
//Thread.Sleep(2000);
p.WaitForExit();
using (StreamReader rd = p.StandardOutput)
{
result = rd.ReadToEnd();
p.Close();
String result1 = String.Copy(result);
}
Is there another method to read text from the console while it is open?
You can use the OutputDataReceived of the Process class
string result = string.Empty;
using (Process process = new Process())
{
process.StartInfo = st; // your ProcessStartInfo
StringBuilder resultBuilder = new StringBuilder();
process.OutputDataReceived += (sender, e) =>
{
st.AppendLine(e.Data);
};
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
result = resultBuilder.ToString();
}
You simply add an event handler to the Process's OutputDataReceived event that gets called whenever the process outputs a line.
Then you need to call BeginOutputReadLine() after the process has been started to begin receiving those events.
In this example, I still wait for the process to exit just to complete the code. Of course you don't need to wait, the events occure while the process is running. So you can store your process variable in a member and dispose it later or even subscribe to its Exited event to get informed when the process terminates.
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"!