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