11

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?

Community
  • 1
  • 1
Ryan
  • 23,871
  • 24
  • 86
  • 132
  • You may be interested in the [MedallionShell](https://github.com/madelson/MedallionShell) library, which makes it easy to work with process io streams and assign timeouts to processes – ChaseMedallion Aug 30 '14 at 00:07

7 Answers7

26

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();
Community
  • 1
  • 1
joce
  • 9,624
  • 19
  • 56
  • 74
  • Please also explain how to find out when the received data is complete. I keep getting those events after `WaitForExit` has returned and the program moved on. The received data is incomplete so this is not safe. I can observe a hang (process won't exit in time) when receiving longer output (short output is always okay), so the synchronous read hang could be true. – ygoe Aug 01 '20 at 13:37
5

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.

Oded
  • 489,969
  • 99
  • 883
  • 1,009
  • 5
    If you do this, don't forget to set the EnableRaisingEvents property on the Process class. Gets me every time. – JohnC Apr 19 '11 at 15:10
  • 2
    According to the MSDN doco EnableRaisingEvents only applies to the Exited event not OutputDataReceived. http://msdn.microsoft.com/en-us/library/system.diagnostics.process.enableraisingevents.aspx – Ryan Apr 19 '11 at 16:31
  • I wonder why my OutputDataReceived event is never firing... Upvoted JohnC too quickly it seems. – Jason Goemaat Aug 23 '14 at 03:12
2

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);
joce
  • 9,624
  • 19
  • 56
  • 74
Bala R
  • 107,317
  • 23
  • 199
  • 210
1
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();
}
Christian Specht
  • 35,843
  • 15
  • 128
  • 182
Amit Jha
  • 11
  • 1
0

Just add everything from the first example below the WaitForExit() call to the second example.

Christian Specht
  • 35,843
  • 15
  • 128
  • 182
Charles Lambert
  • 5,042
  • 26
  • 47
0

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.

JohnC
  • 844
  • 4
  • 10
0

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);
}
Caelis
  • 131
  • 2
  • 6