Надежный способ запустить процесс, прочитать весь вывод и дождаться тайм-аута - PullRequest
2 голосов
/ 01 августа 2020

. NET Core имеет класс Process, который может запускать процесс. Вы также можете прочитать его потоки stdout и stderr и написать что-нибудь в stdin. И вы можете дождаться завершения процесса в указанное время. Проблема в том, что для этого требуется огромное количество кода, и он по-прежнему не работает правильно во всех ситуациях.

При синхронном чтении вы не пропустите ни одной строки вывода и точно скажете, когда у вас есть весь вывод. Но он может заблокироваться, если вызываемая программа генерирует слишком большой объем вывода (> 4 кБ?).

Асинхронное чтение должно разрешить блокировку «переполнения буфера», но не может сказать вам, когда у вас есть весь вывод. И он может пропустить какой-то результат в начале.

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

Итак, я ищу реализацию, которая может:

  • Запуск процесса с аргументами
  • Позволяет мне отправлять данные на его вход
  • Получает мне полное содержимое его вывода (stdout и stderr)
  • Ожидает заданное время и завершает процесс, если он не вернул вовремя
  • Позволяет мне двигаться дальше, когда у меня будет окончательный результат (код тайм-аута / выхода и весь вывод, поэтому far)

Редакция 2020 для. NET Core 3.1 на Windows и Linux.

Также неплохо было бы дождаться процесса асинхронно. У меня есть какой-то код для этого, и опять же, он огромен для того, что он делает.

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

Вот что у меня есть сейчас, и он не работает для большего объема: (процесс не завершается и его нужно убить)

var psi = new ProcessStartInfo
{
    FileName = "dotnet",
    UseShellExecute = false,
    CreateNoWindow = true,
    RedirectStandardOutput = true,
    RedirectStandardError = true
};
psi.ArgumentList.Add("--list-runtimes");   // A few pages output on my PC
string standardOutput;
string standardError;
using (var process = Process.Start(psi))
{
    bool timedOut = false;
    // Sync version
    if (!process.WaitForExit(10000))
    {
        try
        {
            // Try to clean things up
            process.Kill();
        }
        catch
        {
            // Might have exited after all during the short period of time before
            // calling Kill(). And if it fails for other reasons, we can't help it here.
        }
        timedOut = true;
    }
    // BEGIN Async alternative
    using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
    using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
    try
    {
        await process.WaitForExitAsync(cts.Token);
    }
    catch (OperationCanceledException ex) when (ex.CancellationToken == cts.Token)
    {
        try
        {
            // Try to clean things up
            process.Kill();
        }
        catch
        {
            // Might have exited after all during the short period of time before
            // calling Kill(). And if it fails for other reasons, we can't help it here.
        }
        timedOut = true;
    }
    // END Async alternative

    standardOutput = process.StandardOutput.ReadToEnd();
    standardError = process.StandardError.ReadToEnd();

    if (timedOut)
    {
        logger?.LogError($"The command did not complete in time.\n" +
            $"Output: {standardOutput.TrimEnd()}\nError: {standardError.TrimEnd()}");
        standardOutput = null;
    }
    else if (process.ExitCode != 0)
    {
        logger?.LogError($"The command failed with exit code {process.ExitCode}.\n" +
            $"Output: {standardOutput.TrimEnd()}\nError: {standardError.TrimEnd()}");
        standardOutput = null;
    }
}
...