ProcessStartInfo hanging on “WaitForExit”? Why?

前端 未结 22 1839
佛祖请我去吃肉
佛祖请我去吃肉 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:04

    Let us call the sample code posted here the redirector and the other program the redirected. If it were me then I would probably write a test redirected program that can be used to duplicate the problem.

    So I did. For test data I used the ECMA-334 C# Language Specificationv PDF; it is about 5MB. The following is the important part of that.

    StreamReader stream = null;
    try { stream = new StreamReader(Path); }
    catch (Exception ex)
    {
        Console.Error.WriteLine("Input open error: " + ex.Message);
        return;
    }
    Console.SetIn(stream);
    int datasize = 0;
    try
    {
        string record = Console.ReadLine();
        while (record != null)
        {
            datasize += record.Length + 2;
            record = Console.ReadLine();
            Console.WriteLine(record);
        }
    }
    catch (Exception ex)
    {
        Console.Error.WriteLine($"Error: {ex.Message}");
        return;
    }
    

    The datasize value does not match the actual file size but that does not matter. It is not clear if a PDF file always uses both CR and LF at the end of lines but that does not matter for this. You can use any other large text file to test with.

    Using that the sample redirector code hangs when I write the large amount of data but not when I write a small amount.

    I tried very much to somehow trace the execution of that code and I could not. I commented out the lines of the redirected program that disabled creation of a console for the redirected program to try to get a separate console window but I could not.

    Then I found How to start a console app in a new window, the parent’s window, or no window. So apparently we cannot (easily) have a separate console when one console program starts another console program without ShellExecute and since ShellExecute does not support redirection we must share a console, even if we specify no window for the other process.

    I assume that if the redirected program fills up a buffer somewhere then it must wait for the data to be read and if at that point no data is read by the redirector then it is a deadlock.

    The solution is to not use ReadToEnd and to read the data while the data is being written but it is not necessary to use asynchronous reads. The solution can be quite simple. The following works for me with the 5 MB PDF.

    ProcessStartInfo info = new ProcessStartInfo(TheProgram);
    info.CreateNoWindow = true;
    info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
    info.RedirectStandardOutput = true;
    info.UseShellExecute = false;
    Process p = Process.Start(info);
    string record = p.StandardOutput.ReadLine();
    while (record != null)
    {
        Console.WriteLine(record);
        record = p.StandardOutput.ReadLine();
    }
    p.WaitForExit();
    

    Another possibility is to use a GUI program to do the redirection. The preceding code works in a WPF application except with obvious modifications.

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

    Credit to EM0 for https://stackoverflow.com/a/17600012/4151626

    The other solutions (including EM0's) still deadlocked for my application, due to internal timeouts and the use of both StandardOutput and StandardError by the spawned application. Here is what worked for me:

    Process p = new Process()
    {
      StartInfo = new ProcessStartInfo()
      {
        FileName = exe,
        Arguments = args,
        UseShellExecute = false,
        RedirectStandardOutput = true,
        RedirectStandardError = true
      }
    };
    p.Start();
    
    string cv_error = null;
    Thread et = new Thread(() => { cv_error = p.StandardError.ReadToEnd(); });
    et.Start();
    
    string cv_out = null;
    Thread ot = new Thread(() => { cv_out = p.StandardOutput.ReadToEnd(); });
    ot.Start();
    
    p.WaitForExit();
    ot.Join();
    et.Join();
    

    Edit: added initialization of StartInfo to code sample

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

    None of the answers above is doing the job.

    Rob solution hangs and 'Mark Byers' solution get the disposed exception.(I tried the "solutions" of the other answers).

    So I decided to suggest another solution:

    public void GetProcessOutputWithTimeout(Process process, int timeoutSec, CancellationToken token, out string output, out int exitCode)
    {
        string outputLocal = "";  int localExitCode = -1;
        var task = System.Threading.Tasks.Task.Factory.StartNew(() =>
        {
            outputLocal = process.StandardOutput.ReadToEnd();
            process.WaitForExit();
            localExitCode = process.ExitCode;
        }, token);
    
        if (task.Wait(timeoutSec, token))
        {
            output = outputLocal;
            exitCode = localExitCode;
        }
        else
        {
            exitCode = -1;
            output = "";
        }
    }
    
    using (var process = new Process())
    {
        process.StartInfo = ...;
        process.Start();
        string outputUnicode; int exitCode;
        GetProcessOutputWithTimeout(process, PROCESS_TIMEOUT, out outputUnicode, out exitCode);
    }
    

    This code debugged and works perfectly.

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

    None of those answers helped me, but this solution worked fine with handling hangs

    https://stackoverflow.com/a/60355879/10522960

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

    I thing that this is simple and better approach (we don't need AutoResetEvent):

    public static string GGSCIShell(string Path, string Command)
    {
        using (Process process = new Process())
        {
            process.StartInfo.WorkingDirectory = Path;
            process.StartInfo.FileName = Path + @"\ggsci.exe";
            process.StartInfo.CreateNoWindow = true;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardInput = true;
            process.StartInfo.UseShellExecute = false;
    
            StringBuilder output = new StringBuilder();
            process.OutputDataReceived += (sender, e) =>
            {
                if (e.Data != null)
                {
                    output.AppendLine(e.Data);
                }
            };
    
            process.Start();
            process.StandardInput.WriteLine(Command);
            process.BeginOutputReadLine();
    
    
            int timeoutParts = 10;
            int timeoutPart = (int)TIMEOUT / timeoutParts;
            do
            {
                Thread.Sleep(500);//sometimes halv scond is enough to empty output buff (therefore "exit" will be accepted without "timeoutPart" waiting)
                process.StandardInput.WriteLine("exit");
                timeoutParts--;
            }
            while (!process.WaitForExit(timeoutPart) && timeoutParts > 0);
    
            if (timeoutParts <= 0)
            {
                output.AppendLine("------ GGSCIShell TIMEOUT: " + TIMEOUT + "ms ------");
            }
    
            string result = output.ToString();
            return result;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 03:08

    I know that this is supper old but, after reading this whole page none of the solutions was working for me, although I didn't try Muhammad Rehan as the code was a little hard to follow, although I guess he was on the right track. When I say it didn't work that's not entirely true, sometimes it would work fine, I guess it is something to do with the length of the output before an EOF mark.

    Anyway, the solution that worked for me was to use different threads to read the StandardOutput and StandardError and write the messages.

            StreamWriter sw = null;
            var queue = new ConcurrentQueue<string>();
    
            var flushTask = new System.Timers.Timer(50);
            flushTask.Elapsed += (s, e) =>
            {
                while (!queue.IsEmpty)
                {
                    string line = null;
                    if (queue.TryDequeue(out line))
                        sw.WriteLine(line);
                }
                sw.FlushAsync();
            };
            flushTask.Start();
    
            using (var process = new Process())
            {
                try
                {
                    process.StartInfo.FileName = @"...";
                    process.StartInfo.Arguments = $"...";
                    process.StartInfo.UseShellExecute = false;
                    process.StartInfo.RedirectStandardOutput = true;
                    process.StartInfo.RedirectStandardError = true;
    
                    process.Start();
    
                    var outputRead = Task.Run(() =>
                    {
                        while (!process.StandardOutput.EndOfStream)
                        {
                            queue.Enqueue(process.StandardOutput.ReadLine());
                        }
                    });
    
                    var errorRead = Task.Run(() =>
                    {
                        while (!process.StandardError.EndOfStream)
                        {
                            queue.Enqueue(process.StandardError.ReadLine());
                        }
                    });
    
                    var timeout = new TimeSpan(hours: 0, minutes: 10, seconds: 0);
    
                    if (Task.WaitAll(new[] { outputRead, errorRead }, timeout) &&
                        process.WaitForExit((int)timeout.TotalMilliseconds))
                    {
                        if (process.ExitCode != 0)
                        {
                            throw new Exception($"Failed run... blah blah");
                        }
                    }
                    else
                    {
                        throw new Exception($"process timed out after waiting {timeout}");
                    }
                }
                catch (Exception e)
                {
                    throw new Exception($"Failed to succesfully run the process.....", e);
                }
            }
        }
    

    Hope this helps someone, who thought this could be so hard!

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