ProcessStartInfo hanging on “WaitForExit”? Why?

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

    The documentation for Process.StandardOutput says to read before you wait otherwise you can deadlock, snippet copied below:

     // 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();
    
    0 讨论(0)
  • 2020-11-22 03:01

    I solved it this way:

                Process proc = new Process();
                proc.StartInfo.FileName = batchFile;
                proc.StartInfo.UseShellExecute = false;
                proc.StartInfo.CreateNoWindow = true;
                proc.StartInfo.RedirectStandardError = true;
                proc.StartInfo.RedirectStandardInput = true;
                proc.StartInfo.RedirectStandardOutput = true;
                proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;      
                proc.Start();
                StreamWriter streamWriter = proc.StandardInput;
                StreamReader outputReader = proc.StandardOutput;
                StreamReader errorReader = proc.StandardError;
                while (!outputReader.EndOfStream)
                {
                    string text = outputReader.ReadLine();                    
                    streamWriter.WriteLine(text);
                }
    
                while (!errorReader.EndOfStream)
                {                   
                    string text = errorReader.ReadLine();
                    streamWriter.WriteLine(text);
                }
    
                streamWriter.Close();
                proc.WaitForExit();
    

    I redirected both input, output and error and handled reading from output and error streams. This solution works for SDK 7- 8.1, both for Windows 7 and Windows 8

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

    The problem is that if you redirect StandardOutput and/or StandardError the internal buffer can become full. Whatever order you use, there can be a problem:

    • If you wait for the process to exit before reading StandardOutput the process can block trying to write to it, so the process never ends.
    • If you read from StandardOutput using ReadToEnd then your process can block if the process never closes StandardOutput (for example if it never terminates, or if it is blocked writing to StandardError).

    The solution is to use asynchronous reads to ensure that the buffer doesn't get full. To avoid any deadlocks and collect up all output from both StandardOutput and StandardError you can do this:

    EDIT: See answers below for how avoid an ObjectDisposedException if the timeout occurs.

    using (Process process = new Process())
    {
        process.StartInfo.FileName = filename;
        process.StartInfo.Arguments = arguments;
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardError = true;
    
        StringBuilder output = new StringBuilder();
        StringBuilder error = new StringBuilder();
    
        using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
        using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
        {
            process.OutputDataReceived += (sender, e) => {
                if (e.Data == null)
                {
                    outputWaitHandle.Set();
                }
                else
                {
                    output.AppendLine(e.Data);
                }
            };
            process.ErrorDataReceived += (sender, e) =>
            {
                if (e.Data == null)
                {
                    errorWaitHandle.Set();
                }
                else
                {
                    error.AppendLine(e.Data);
                }
            };
    
            process.Start();
    
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();
    
            if (process.WaitForExit(timeout) &&
                outputWaitHandle.WaitOne(timeout) &&
                errorWaitHandle.WaitOne(timeout))
            {
                // Process completed. Check process.ExitCode here.
            }
            else
            {
                // Timed out.
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 03:02

    This is a more modern awaitable, Task Parallel Library (TPL) based solution for .NET 4.5 and above.

    Usage Example

    try
    {
        var exitCode = await StartProcess(
            "dotnet", 
            "--version", 
            @"C:\",
            10000, 
            Console.Out, 
            Console.Out);
        Console.WriteLine($"Process Exited with Exit Code {exitCode}!");
    }
    catch (TaskCanceledException)
    {
        Console.WriteLine("Process Timed Out!");
    }
    

    Implementation

    public static async Task<int> StartProcess(
        string filename,
        string arguments,
        string workingDirectory= null,
        int? timeout = null,
        TextWriter outputTextWriter = null,
        TextWriter errorTextWriter = null)
    {
        using (var process = new Process()
        {
            StartInfo = new ProcessStartInfo()
            {
                CreateNoWindow = true,
                Arguments = arguments,
                FileName = filename,
                RedirectStandardOutput = outputTextWriter != null,
                RedirectStandardError = errorTextWriter != null,
                UseShellExecute = false,
                WorkingDirectory = workingDirectory
            }
        })
        {
            var cancellationTokenSource = timeout.HasValue ?
                new CancellationTokenSource(timeout.Value) :
                new CancellationTokenSource();
    
            process.Start();
    
            var tasks = new List<Task>(3) { process.WaitForExitAsync(cancellationTokenSource.Token) };
            if (outputTextWriter != null)
            {
                tasks.Add(ReadAsync(
                    x =>
                    {
                        process.OutputDataReceived += x;
                        process.BeginOutputReadLine();
                    },
                    x => process.OutputDataReceived -= x,
                    outputTextWriter,
                    cancellationTokenSource.Token));
            }
    
            if (errorTextWriter != null)
            {
                tasks.Add(ReadAsync(
                    x =>
                    {
                        process.ErrorDataReceived += x;
                        process.BeginErrorReadLine();
                    },
                    x => process.ErrorDataReceived -= x,
                    errorTextWriter,
                    cancellationTokenSource.Token));
            }
    
            await Task.WhenAll(tasks);
            return process.ExitCode;
        }
    }
    
    /// <summary>
    /// Waits asynchronously for the process to exit.
    /// </summary>
    /// <param name="process">The process to wait for cancellation.</param>
    /// <param name="cancellationToken">A cancellation token. If invoked, the task will return
    /// immediately as cancelled.</param>
    /// <returns>A Task representing waiting for the process to end.</returns>
    public static Task WaitForExitAsync(
        this Process process,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        process.EnableRaisingEvents = true;
    
        var taskCompletionSource = new TaskCompletionSource<object>();
    
        EventHandler handler = null;
        handler = (sender, args) =>
        {
            process.Exited -= handler;
            taskCompletionSource.TrySetResult(null);
        };
        process.Exited += handler;
    
        if (cancellationToken != default(CancellationToken))
        {
            cancellationToken.Register(
                () =>
                {
                    process.Exited -= handler;
                    taskCompletionSource.TrySetCanceled();
                });
        }
    
        return taskCompletionSource.Task;
    }
    
    /// <summary>
    /// Reads the data from the specified data recieved event and writes it to the
    /// <paramref name="textWriter"/>.
    /// </summary>
    /// <param name="addHandler">Adds the event handler.</param>
    /// <param name="removeHandler">Removes the event handler.</param>
    /// <param name="textWriter">The text writer.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public static Task ReadAsync(
        this Action<DataReceivedEventHandler> addHandler,
        Action<DataReceivedEventHandler> removeHandler,
        TextWriter textWriter,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        var taskCompletionSource = new TaskCompletionSource<object>();
    
        DataReceivedEventHandler handler = null;
        handler = new DataReceivedEventHandler(
            (sender, e) =>
            {
                if (e.Data == null)
                {
                    removeHandler(handler);
                    taskCompletionSource.TrySetResult(null);
                }
                else
                {
                    textWriter.WriteLine(e.Data);
                }
            });
    
        addHandler(handler);
    
        if (cancellationToken != default(CancellationToken))
        {
            cancellationToken.Register(
                () =>
                {
                    removeHandler(handler);
                    taskCompletionSource.TrySetCanceled();
                });
        }
    
        return taskCompletionSource.Task;
    }
    
    0 讨论(0)
  • 2020-11-22 03:02

    I tried to make a class that would solve your problem using asynchronous stream read, by taking in account Mark Byers, Rob, stevejay answers. Doing so I realised that there is a bug related to asynchronous process output stream read.

    I reported that bug at Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/3119134

    Summary:

    You can't do that:

    process.BeginOutputReadLine(); process.Start();

    You will receive System.InvalidOperationException : StandardOut has not been redirected or the process hasn't started yet.

    ============================================================================================================================

    Then you have to start asynchronous output read after the process is started:

    process.Start(); process.BeginOutputReadLine();

    Doing so, make a race condition because the output stream can receive data before you set it to asynchronous:

    process.Start(); 
    // Here the operating system could give the cpu to another thread.  
    // For example, the newly created thread (Process) and it could start writing to the output
    // immediately before next line would execute. 
    // That create a race condition.
    process.BeginOutputReadLine();
    

    ============================================================================================================================

    Then some people could say that you just have to read the stream before you set it to asynchronous. But the same problem occurs. There will be a race condition between the synchronous read and set the stream into asynchronous mode.

    ============================================================================================================================

    There is no way to acheive safe asynchronous read of an output stream of a process in the actual way "Process" and "ProcessStartInfo" has been designed.

    You are probably better using asynchronous read like suggested by other users for your case. But you should be aware that you could miss some information due to race condition.

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

    The problem with unhandled ObjectDisposedException happens when the process is timed out. In such case the other parts of the condition:

    if (process.WaitForExit(timeout) 
        && outputWaitHandle.WaitOne(timeout) 
        && errorWaitHandle.WaitOne(timeout))
    

    are not executed. I resolved this problem in a following way:

    using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
    using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
    {
        using (Process process = new Process())
        {
            // preparing ProcessStartInfo
    
            try
            {
                process.OutputDataReceived += (sender, e) =>
                    {
                        if (e.Data == null)
                        {
                            outputWaitHandle.Set();
                        }
                        else
                        {
                            outputBuilder.AppendLine(e.Data);
                        }
                    };
                process.ErrorDataReceived += (sender, e) =>
                    {
                        if (e.Data == null)
                        {
                            errorWaitHandle.Set();
                        }
                        else
                        {
                            errorBuilder.AppendLine(e.Data);
                        }
                    };
    
                process.Start();
    
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();
    
                if (process.WaitForExit(timeout))
                {
                    exitCode = process.ExitCode;
                }
                else
                {
                    // timed out
                }
    
                output = outputBuilder.ToString();
            }
            finally
            {
                outputWaitHandle.WaitOne(timeout);
                errorWaitHandle.WaitOne(timeout);
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题