ProcessStartInfo hanging on “WaitForExit”? Why?

前端 未结 22 1861
佛祖请我去吃肉
佛祖请我去吃肉 2020-11-22 02:59

I have the following code:

info = new System.Diagnostics.ProcessStartInfo(\"TheProgram.exe\", String.Join(\" \", args));
info.CreateNoWindow = true;
info.Win         


        
相关标签:
22条回答
  • 2020-11-22 03:16

    Introduction

    Currently accepted answer doesn't work (throws exception) and there are too many workarounds but no complete code. This is obviously wasting lots of people's time because this is a popular question.

    Combining Mark Byers' answer and Karol Tyl's answer I wrote full code based on how I want to use the Process.Start method.

    Usage

    I have used it to create progress dialog around git commands. This is how I've used it:

        private bool Run(string fullCommand)
        {
            Error = "";
            int timeout = 5000;
    
            var result = ProcessNoBS.Start(
                filename: @"C:\Program Files\Git\cmd\git.exe",
                arguments: fullCommand,
                timeoutInMs: timeout,
                workingDir: @"C:\test");
    
            if (result.hasTimedOut)
            {
                Error = String.Format("Timeout ({0} sec)", timeout/1000);
                return false;
            }
    
            if (result.ExitCode != 0)
            {
                Error = (String.IsNullOrWhiteSpace(result.stderr)) 
                    ? result.stdout : result.stderr;
                return false;
            }
    
            return true;
        }
    

    In theory you can also combine stdout and stderr, but I haven't tested that.

    Code

    public struct ProcessResult
    {
        public string stdout;
        public string stderr;
        public bool hasTimedOut;
        private int? exitCode;
    
        public ProcessResult(bool hasTimedOut = true)
        {
            this.hasTimedOut = hasTimedOut;
            stdout = null;
            stderr = null;
            exitCode = null;
        }
    
        public int ExitCode
        {
            get 
            {
                if (hasTimedOut)
                    throw new InvalidOperationException(
                        "There was no exit code - process has timed out.");
    
                return (int)exitCode;
            }
            set
            {
                exitCode = value;
            }
        }
    }
    
    public class ProcessNoBS
    {
        public static ProcessResult Start(string filename, string arguments,
            string workingDir = null, int timeoutInMs = 5000,
            bool combineStdoutAndStderr = false)
        {
            using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
            using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
            {
                using (var process = new Process())
                {
                    var info = new ProcessStartInfo();
    
                    info.CreateNoWindow = true;
                    info.FileName = filename;
                    info.Arguments = arguments;
                    info.UseShellExecute = false;
                    info.RedirectStandardOutput = true;
                    info.RedirectStandardError = true;
    
                    if (workingDir != null)
                        info.WorkingDirectory = workingDir;
    
                    process.StartInfo = info;
    
                    StringBuilder stdout = new StringBuilder();
                    StringBuilder stderr = combineStdoutAndStderr
                        ? stdout : new StringBuilder();
    
                    var result = new ProcessResult();
    
                    try
                    {
                        process.OutputDataReceived += (sender, e) =>
                        {
                            if (e.Data == null)
                                outputWaitHandle.Set();
                            else
                                stdout.AppendLine(e.Data);
                        };
                        process.ErrorDataReceived += (sender, e) =>
                        {
                            if (e.Data == null)
                                errorWaitHandle.Set();
                            else
                                stderr.AppendLine(e.Data);
                        };
    
                        process.Start();
    
                        process.BeginOutputReadLine();
                        process.BeginErrorReadLine();
    
                        if (process.WaitForExit(timeoutInMs))
                            result.ExitCode = process.ExitCode;
                        // else process has timed out 
                        // but that's already default ProcessResult
    
                        result.stdout = stdout.ToString();
                        if (combineStdoutAndStderr)
                            result.stderr = null;
                        else
                            result.stderr = stderr.ToString();
    
                        return result;
                    }
                    finally
                    {
                        outputWaitHandle.WaitOne(timeoutInMs);
                        errorWaitHandle.WaitOne(timeoutInMs);
                    }
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 03:17

    Mark Byers' answer is excellent, but I would just add the following:

    The OutputDataReceived and ErrorDataReceived delegates need to be removed before the outputWaitHandle and errorWaitHandle get disposed. If the process continues to output data after the timeout has been exceeded and then terminates, the outputWaitHandle and errorWaitHandle variables will be accessed after being disposed.

    (FYI I had to add this caveat as an answer as I couldn't comment on his post.)

    0 讨论(0)
  • 2020-11-22 03:17

    This post maybe outdated but i found out the main cause why it usually hang is due to stack overflow for the redirectStandardoutput or if you have redirectStandarderror.

    As the output data or the error data is large, it will cause a hang time as it is still processing for indefinite duration.

    so to resolve this issue:

    p.StartInfo.RedirectStandardoutput = False
    p.StartInfo.RedirectStandarderror = False
    
    0 讨论(0)
  • 2020-11-22 03:19

    We have this issue as well (or a variant).

    Try the following:

    1) Add a timeout to p.WaitForExit(nnnn); where nnnn is in milliseconds.

    2) Put the ReadToEnd call before the WaitForExit call. This is what we've seen MS recommend.

    0 讨论(0)
  • 2020-11-22 03:19

    Workaround I ended up using to avoid all the complexity:

    var outputFile = Path.GetTempFileName();
    info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args) + " > " + outputFile + " 2>&1");
    info.CreateNoWindow = true;
    info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
    info.UseShellExecute = false;
    System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
    p.WaitForExit();
    Console.WriteLine(File.ReadAllText(outputFile)); //need the StandardOutput contents
    

    So I create a temp file, redirect both the output and error to it by using > outputfile > 2>&1 and then just read the file after the process has finished.

    The other solutions are fine for scenarios where you want to do other stuff with the output, but for simple stuff this avoids a lot of complexity.

    0 讨论(0)
  • 2020-11-22 03:21

    I was having the same issue, but the reason was different. It would however happen under Windows 8, but not under Windows 7. The following line seems to have caused the problem.

    pProcess.StartInfo.UseShellExecute = False
    

    The solution was to NOT disable UseShellExecute. I now received a Shell popup window, which is unwanted, but much better than the program waiting for nothing particular to happen. So I added the following work-around for that:

    pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
    

    Now the only thing bothering me is to why this is happening under Windows 8 in the first place.

    0 讨论(0)
提交回复
热议问题