[C#] “WaitForExit”에 ProcessStartInfo가 걸려 있습니까? 왜?

다음 코드가 있습니다.

info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args));
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(p.StandardOutput.ReadToEnd()); //need the StandardOutput contents

시작하는 프로세스의 출력 길이가 약 7MB라는 것을 알고 있습니다. Windows 콘솔에서 실행하면 정상적으로 작동합니다. 불행히도 프로그래밍 방식으로 이것은 WaitForExit에서 무기한 정지됩니다. 더 작은 출력 (예 : 3KB)의 경우 코드가 멈추지 않습니다.

ProcessStartInfo의 내부 StandardOutput이 7MB를 버퍼링 할 수 없습니까? 그렇다면 대신 어떻게해야합니까? 그렇지 않은 경우, 내가 뭘 잘못하고 있습니까?



답변

문제는 리디렉션 StandardOutput및 / 또는 StandardError내부 버퍼가 가득 찰 수 있다는 것입니다. 어떤 주문을 사용하든 문제가있을 수 있습니다.

  • 프로세스를 읽기 전에 프로세스가 종료 될 때까지 기다리면 프로세스 StandardOutput쓰기를 차단할 수 있으므로 프로세스가 종료되지 않습니다.
  • 당신이 읽을 경우 StandardOutputReadToEnd를 사용하여 다음 당신의 프로세스는 프로세스가 종료되지 않을 경우 차단할 수 있습니다 StandardOutput(이에 쓰기를 차단하는 경우가 종료되지 않을 경우 예를 들어, 또는 StandardError).

해결책은 비동기 읽기를 사용하여 버퍼가 가득 차지 않도록하는 것입니다. 어떤 교착 상태를 방지하고 모두에서 모든 출력을 수집하는 방법 StandardOutputStandardError이 작업을 수행 할 수 있습니다 :

편집 : 시간 초과가 발생 하면 ObjectDisposedException을 피하는 방법은 아래 답변을 참조하십시오 .

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.
        }
    }
}


답변

문서 에 대한은 Process.StandardOutput당신이 그렇지 않으면 당신은 교착 수 있습니다 기다려야하기 전에 아래 복사 니펫을 읽고 말한다 :

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


답변

Mark Byers의 답변은 훌륭하지만 다음을 추가합니다.

OutputDataReceivedErrorDataReceived대표는 전에 제거해야 outputWaitHandle하고 errorWaitHandle배치받을. 제한 시간이 초과 된 후 프로세스가 계속 데이터를 출력 한 후 종료되면 outputWaitHandleerrorWaitHandle변수는 폐기 된 후 액세스됩니다.

(참고로 나는 그의 게시물에 대해 언급 할 수 없었기 때문에이 경고를 대답으로 추가해야했습니다.)


답변

이것은 .NET 4.5 이상을위한보다 현대적이고 대기 가능한 TPL (Task Parallel Library) 기반 솔루션입니다.

사용 예

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

이행

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


답변

처리 시간이 초과되면 처리되지 않은 ObjectDisposedException 관련 문제가 발생합니다. 그러한 경우 조건의 다른 부분 :

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

실행되지 않습니다. 이 문제는 다음과 같은 방식으로 해결되었습니다.

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


답변

Rob은 이에 응답하여 몇 시간의 시련을 더 절약했습니다. 대기하기 전에 출력 / 오류 버퍼를 읽으십시오.

// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();


답변

우리는이 문제 (또는 변형)도 가지고 있습니다.

다음을 시도하십시오 :

1) p.WaitForExit (nnnn)에 타임 아웃을 추가하십시오. 여기서 nnnn은 밀리 초입니다.

2) ReadForEnd 호출을 WaitForExit 호출 앞에 두십시오. 이것은 이다 우리는 MS가 추천 무엇을 본 적이.