Вопрос

У меня есть следующий код:

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

Я знаю, что результат процесса, который я запускаю, имеет длину около 7 МБ.Запуск в консоли Windows работает нормально.К сожалению, программно это зависает на неопределенный срок в WaitForExit.Также обратите внимание, что код НЕ зависает для выходных данных меньшего размера (например, 3 КБ).

Возможно ли, что внутренний StandardOutput в ProcessStartInfo не может буферизовать 7 МБ?Если да, то что мне делать вместо этого?Если нет, то что я делаю не так?

Это было полезно?

Решение

Проблема в том, что если вы перенаправите StandardOutput и/или StandardError внутренний буфер может переполниться.Какой бы порядок вы ни использовали, может возникнуть проблема:

  • Если вы дождетесь завершения процесса, прежде чем читать StandardOutput процесс может заблокировать попытку записи в него, поэтому процесс никогда не завершается.
  • Если вы читаете из StandardOutput используя ReadToEnd, затем твой процесс может заблокироваться, если процесс никогда не закрывается StandardOutput (например, если он никогда не завершается или заблокирована запись в StandardError).

Решение состоит в том, чтобы использовать асинхронное чтение, чтобы гарантировать, что буфер не будет заполнен.Чтобы избежать тупиковых ситуаций и собрать все выходные данные обоих StandardOutput и StandardError вы можете сделать это:

РЕДАКТИРОВАТЬ:См. ответы ниже, чтобы узнать, как избежать Объектдиспоуздисцептион если произойдет тайм-аут.

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

Ответ Марка Байерса превосходен, но я бы добавил следующее:делегаты OutputDataReceived и ErrorDataReceived необходимо удалить перед удалением выходных данныхWaitHandle и errorWaitHandle.Если процесс продолжает выводить данные после превышения таймаута, а затем завершается, переменные outputWaitHandle и errorWaitHandle будут доступны после их удаления.

(К вашему сведению, мне пришлось добавить это предостережение в качестве ответа, поскольку я не мог комментировать его сообщение.)

Проблема с необработанным ObjectDisposeException возникает, когда время ожидания процесса истекло.В таком случае остальные части условия:

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

Это более современное ожидаемое решение на основе Task Parallel Library (TPL) для .NET 4.5 и более поздних версий.

Пример использования

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
        }
    })
    {
        process.Start();
        var cancellationTokenSource = timeout.HasValue ?
            new CancellationTokenSource(timeout.Value) :
            new CancellationTokenSource();

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

Роб ответил на него и сэкономил мне еще несколько часов испытаний.Перед ожиданием прочтите буфер вывода/ошибки:

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

У нас тоже есть эта проблема (или ее вариант).

Попробуйте следующее:

1) Добавьте таймаут в p.WaitForExit(nnnn);где nnnn — миллисекунды.

2) Поместите вызов ReadToEnd перед вызовом WaitForExit.Этот является то, что мы видели, рекомендует MS.

Кредит ЕМ0 для https://stackoverflow.com/a/17600012/4151626

Другие решения (включая EM0) по-прежнему заблокированы для моего приложения из-за внутренних тайм-аутов и использования StandardOutput и StandardError порожденным приложением.Вот что сработало для меня:

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

Редактировать:добавлена ​​инициализация StartInfo в пример кода

Я решил это следующим образом:

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

Я перенаправил ввод, вывод и ошибки и обработал чтение из потоков вывода и ошибок.Это решение работает для SDK 7–8.1, как для Windows 7, так и для Windows 8.

Этот пост, возможно, устарел, но я выяснил, что основная причина, по которой он обычно зависает, связана с переполнением стека для вывода redirectStandardoutput или с ошибкой redirectStandarderror.

Поскольку выходные данные или данные об ошибках имеют большой размер, это приведет к зависанию, поскольку обработка все еще продолжается в течение неопределенного времени.

поэтому, чтобы решить эту проблему:

p.StartInfo.RedirectStandardoutput = False
p.StartInfo.RedirectStandarderror = False

Я попытался создать класс, который решил бы вашу проблему с использованием асинхронного чтения потока, принимая во внимание ответы Марка Байерса, Роба и Стивджея.При этом я понял, что существует ошибка, связанная с чтением выходного потока асинхронного процесса.

Я сообщил об этой ошибке в Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/3119134

Краткое содержание:

Вы не можете этого сделать:

процесс.BeginOutputReadLine();процесс.Начать();

Вы получите System.InvalidOperationException:Standardout не был перенаправлен или процесс еще не начался.

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

Затем вы должны начать асинхронное выходное чтение после начала процесса:

процесс.Начать();процесс.BeginOutputReadLine();

Это сделайте условие гонки, потому что выходной поток может получать данные, прежде чем установить их на асинхронный:

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

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

Тогда некоторые люди могли бы сказать, что вам просто нужно прочитать поток, прежде чем установить его на асинхронный.Но возникает та же проблема.Между синхронным чтением будет состояние гонки и установить поток в асинхронный режим.

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

Невозможно получить безопасное асинхронное чтение выходного потока процесса фактическим способом «процесс» и «ProcessStartInfo».

Вероятно, вам лучше использовать асинхронное чтение, как это предлагают другие пользователи для вашего случая.Но вы должны знать, что можете пропустить некоторую информацию из-за состояния гонки.

Ни один из приведенных выше ответов не выполняет эту работу.

Решение Роба зависает, а решение «Марк Байерс» получает удаленное исключение. (Я пробовал «решения» других ответов).

Поэтому я решил предложить другое решение:

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

Этот код отлажен и работает отлично.

Введение

Принятый на данный момент ответ не работает (выдает исключение), и существует слишком много обходных путей, но нет полного кода.Очевидно, что это трата времени многих людей, потому что это популярный вопрос.

Объединив ответ Марка Байерса и ответ Карола Тила, я написал полный код, основанный на том, как я хочу использовать метод Process.Start.

Применение

Я использовал его для создания диалога прогресса вокруг команд git.Вот как я это использовал:

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

Теоретически вы также можете комбинировать stdout и stderr, но я это не проверял.

Код

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

Я знаю, что это старый ужин, но после прочтения всей этой страницы ни одно из решений не сработало для меня, хотя я не пробовал Мухаммеда Рехана, поскольку коду было немного сложно следовать, хотя я думаю, что он был на правильном пути .Когда я говорю, что это не сработало, это не совсем так, иногда это работало нормально, я думаю, это как-то связано с длиной вывода до отметки EOF.

В любом случае, решение, которое сработало для меня, состояло в том, чтобы использовать разные потоки для чтения StandardOutput и StandardError и записи сообщений.

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

Надеюсь, это поможет кому-то, кто думал, что это может быть так сложно!

Прочитав все сообщения здесь, я остановился на консолидированном решении Марко Авлияша.Однако, это не решило всех моих проблем.

В нашей среде есть служба Windows, которая должна запускать сотни различных файлов .bat .cmd .exe,...и т. д.файлы, которые накопились за годы и были написаны разными людьми и в разных стилях.Мы не контролируем написание программ и сценариев, мы просто несем ответственность за планирование, запуск и отчеты об успехе/неуспехе.

Итак, я попробовал практически все предложенные здесь предложения с разной степенью успеха.Ответ Марко был почти идеальным, но при запуске как службы он не всегда перехватывал стандартный вывод.Я так и не понял, почему бы и нет.

Единственное найденное нами решение, которое работает во ВСЕХ наших случаях, заключается в следующем: http://csharptest.net/319/using-the-processrunner-class/index.html

Обходной путь, который я использовал, чтобы избежать всей сложности:

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

Поэтому я создаю временный файл, перенаправляю в него как вывод, так и ошибку, используя > outputfile > 2>&1 а затем просто прочитайте файл после завершения процесса.

Другие решения подходят для сценариев, в которых вы хотите выполнять другие действия с выходными данными, но для простых задач это позволяет избежать больших сложностей.

Я считаю, что это простой и лучший подход (нам не нужно 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;
    }
}

Я думаю, что с помощью async можно получить более элегантное решение и не иметь взаимоблокировок даже при использовании как StandardOutput, так и StandardError:

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;

    process.Start();

    var tStandardOutput = process.StandardOutput.ReadToEndAsync();
    var tStandardError = process.StandardError.ReadToEndAsync();

    if (process.WaitForExit(timeout))
    {
        string output = await tStandardOutput;
        string errors = await tStandardError;

        // Process completed. Check process.ExitCode here.
    }
    else
    {
        // Timed out.
    }
}

Это основано на ответе Марка Байерса.Если вы не используете асинхронный метод, вы можете использовать string output = tStandardOutput.result; вместо await

Давайте назовем приведенный здесь пример кода перенаправлением, а другую программу — перенаправлением.Если бы это был я, я бы, вероятно, написал тестовую программу с перенаправлением, которую можно было бы использовать для дублирования проблемы.

Так я и сделал.В качестве тестовых данных я использовал спецификацию языка C# ECMA-334 в формате PDF;это около 5 МБ.Ниже приводится важная часть этого.

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

Значение datasize не соответствует фактическому размеру файла, но это не имеет значения.Неясно, всегда ли в PDF-файле используются как CR, так и LF в конце строк, но в данном случае это не имеет значения.Для тестирования вы можете использовать любой другой большой текстовый файл.

Используя это, пример кода перенаправителя зависает, когда я записываю большой объем данных, но не когда я пишу небольшой объем.

Я очень старался как-то отследить выполнение того кода и не смог.Я закомментировал строки перенаправленной программы, отключившие создание консоли для перенаправленной программы, чтобы попытаться получить отдельное окно консоли, но не смог.

Потом я нашел Как запустить консольное приложение в новом окне, родительском окне или без окна.Таким образом, очевидно, что мы не можем (легко) иметь отдельную консоль, когда одна консольная программа запускает другую консольную программу без ShellExecute, и поскольку ShellExecute не поддерживает перенаправление, мы должны использовать консоль совместно, даже если мы не указываем окно для другого процесса.

Я предполагаю, что если перенаправленная программа заполняет где-то буфер, она должна ждать, пока данные будут прочитаны, и если в этот момент перенаправитель не читает данные, то это взаимоблокировка.

Решение состоит в том, чтобы не использовать ReadToEnd и читать данные во время записи данных, но нет необходимости использовать асинхронное чтение.Решение может быть довольно простым.Следующее работает для меня с PDF-файлом размером 5 МБ.

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

Другая возможность — использовать программу с графическим интерфейсом для перенаправления.Приведенный выше код работает в приложении WPF, за исключением очевидных изменений.

У меня была такая же проблема, но причина была в другом.Однако это произойдет в Windows 8, но не в Windows 7.Следующая строка, похоже, вызвала проблему.

pProcess.StartInfo.UseShellExecute = False

Решением было НЕ отключать UseShellExecute.Теперь я получил всплывающее окно Shell, что нежелательно, но намного лучше, чем программа, ожидающая ничего особенного.Поэтому я добавил для этого следующий обходной путь:

pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden

Теперь единственное, что меня беспокоит, это то, почему это вообще происходит в Windows 8.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top