c# ProcessStartInfo.开始读取输出,但有一个超时

本文关键字:有一个 超时 输出 ProcessStartInfo 开始 读取 | 更新日期: 2023-09-27 17:50:29

如果您想启动另一个进程并等待(超时)完成,您可以使用以下命令(来自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...");

如果你想启动另一个进程并读取它的输出,那么你可以使用以下模式(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();

如何将两者结合起来读取所有输入,而不会陷入死锁并在运行进程出错时超时?

c# ProcessStartInfo.开始读取输出,但有一个超时

如果输出缓冲区中填充的数据超过4KB,则此技术将挂起。一种更简单的方法是注册委托,以便在向输出流写入内容时得到通知。我之前在另一篇文章中已经建议过这个方法:

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

您不必将两者结合- Process类有一个事件,当输出发送到StandardOutput - OutputDataReceived时触发。

如果您订阅了事件,您将能够在输出到达时读取输出,并且在主程序循环中您仍然可以超时。

您可以尝试将第一个方法修改为如下内容

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

您也可以使用APM,如下所示:

为ReadToEnd调用定义一个委托:

private delegate string ReadToEndDelegate();

然后使用委托像这样调用方法:

ReadToEndDelegate asyncCall = reader.ReadToEnd;
IAsyncResult asyncResult = asyncCall.BeginInvoke(null, null);
asyncResult.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(10));
asyncCall.EndInvoke(asyncResult);

只需将WaitForExit()调用下面的第一个示例中的所有内容添加到第二个示例中。

在处理交互式提示时,上述答案都不适合我。(我的命令有时会向用户提示一个问题,这也应该被超时覆盖)。

这是我的解决方案。缺点是,如果我们在超时时间内运行,我不会得到任何输出。

ReadToEnd()阻塞了执行,所以我们必须在另一个线程中运行它,如果进程运行到指定的超时时间,则杀死该线程。

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