Ryan
Ryan

Reputation: 24432

c# ProcessStartInfo.Start - reading output but with a timeout

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?

Upvotes: 11

Views: 25709

Answers (7)

Caelis
Caelis

Reputation: 311

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);
}

Upvotes: 1

Charles Lambert
Charles Lambert

Reputation: 5132

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

Upvotes: 0

Amit Jha
Amit Jha

Reputation: 11

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();
}

Upvotes: 1

joce
joce

Reputation: 9892

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();

Upvotes: 27

Bala R
Bala R

Reputation: 108957

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);

Upvotes: 2

JohnC
JohnC

Reputation: 842

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.

Upvotes: 0

Oded
Oded

Reputation: 499062

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.

Upvotes: 5

Related Questions