Мне нужно создать дочерний процесс, который является консольным приложением, и записать его вывод.
Я написал следующий код для метода:
string retMessage = String.Empty;
ProcessStartInfo startInfo = new ProcessStartInfo();
Process p = new Process();
startInfo.CreateNoWindow = true;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardInput = true;
startInfo.UseShellExecute = false;
startInfo.Arguments = command;
startInfo.FileName = exec;
p.StartInfo = startInfo;
p.Start();
p.OutputDataReceived += new DataReceivedEventHandler
(
delegate(object sender, DataReceivedEventArgs e)
{
using (StreamReader output = p.StandardOutput)
{
retMessage = output.ReadToEnd();
}
}
);
p.WaitForExit();
return retMessage;
Однако это ничего не возвращает. Я не верю, что событие OutputDataReceived вызывается, или команда WaitForExit() может блокировать поток, поэтому он никогда не будет выполнять обратный вызов.
Любой совет?
Обновлено: Похоже, я слишком старался с обратным вызовом. Делает:
return p.StandardOutput.ReadToEnd();
Кажется, работает нормально.
Почему не retMessage = e.Data? Это уже строковая переменная :)





Вам нужно вызвать p.Start (), чтобы фактически запустить процесс после того, как вы установили StartInfo. Как бы то ни было, ваша функция, вероятно, зависает при вызове WaitForExit (), потому что процесс на самом деле никогда не запускался.
это была опечатка, так как я удалял код, чтобы сделать образец, я случайно вырезал эту строку.
Вот код, работоспособность которого я проверил. Я использую его для создания MSBuild и прослушивания его вывода:
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += (sender, args) => Console.WriteLine("received output: {0}", args.Data);
process.Start();
process.BeginOutputReadLine();
Важнейшей командой, которая исправляет OP, является добавление BeginOutputReadLine ()
Большое спасибо, @Judah Himango
Я не могу поверить, сколько людей упускают из виду часть «BeginOutputReadLine». Спас мои дни, спасибо!
Чтобы также фиксировать ошибки, добавьте RedirectStandardError = true, process.ErrorDataReceived += (sender, args) => Console.WriteLine(args.Data); и process.BeginErrorReadLine();.
Любопытно, есть ли время между Start () и BeginOutputReadLine (), когда вывод может быть потерян (не передается в OutputDataReceived)?
Похоже, две ваши строки вышли из строя. Вы запускаете процесс перед настройкой обработчика событий для захвата вывода. Возможно, процесс только что завершается до того, как будет добавлен обработчик событий.
Переключите строки вот так.
p.OutputDataReceived += ...
p.Start();
Несмотря на то, что он не отмечен как таковой, это, вероятно, правильный ответ.
У меня это не сработало. РЕДАКТИРОВАНИЕ, которое FlySwat включил в свой ответ, сработало для меня.
@CasperLeonNielsen, то же самое.
Это не правильно. Чтобы это сработало, вам нужно сначала вызвать BeginOutputReadLine, который начинает читать поток stdOut. Вы также можете читать поток напрямую. К сожалению, выполнение создает потоки ввода-вывода только после того, как процесс началось. Вызов BeginOutputReadLine или чтение из потока перед вызовом process.Start() вызовет выброс, потому что поток еще не существует.
Я просто попробовал это, и у меня сработало следующее:
StringBuilder outputBuilder;
ProcessStartInfo processStartInfo;
Process process;
outputBuilder = new StringBuilder();
processStartInfo = new ProcessStartInfo();
processStartInfo.CreateNoWindow = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardInput = true;
processStartInfo.UseShellExecute = false;
processStartInfo.Arguments = "<insert command line arguments here>";
processStartInfo.FileName = "<insert tool path here>";
process = new Process();
process.StartInfo = processStartInfo;
// enable raising events because Process does not raise events by default
process.EnableRaisingEvents = true;
// attach the event handler for OutputDataReceived before starting the process
process.OutputDataReceived += new DataReceivedEventHandler
(
delegate(object sender, DataReceivedEventArgs e)
{
// append the new data to the data already read-in
outputBuilder.Append(e.Data);
}
);
// start the process
// then begin asynchronously reading the output
// then wait for the process to exit
// then cancel asynchronously reading the output
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
process.CancelOutputRead();
// use the output
string output = outputBuilder.ToString();
Я обнаружил, что это немного проще p.StartInfo = startInfo; p.Start(); output = p.StandardOutput.ReadToEnd(); p.WaitForExit();
@Spike, но он работает синхронно. Если вам нужно передать данные на стандартный ввод, это не сработает.
Обратите внимание, что EnableRaisingEvents = true необходимо только для возникновения события Process.Exited.
Это прекрасно работает. Спасибо @ umar-farooq-khawaja
Должен быть "AppendLine"
@zumalifeguard, если вы используете AppendLine, это добавит новые строки, где данные разбиты из-за буферизации. Добавить правильно.
Умар, понятно. Но если вы не добавите новые строки, весь текст превратится в одну большую кляксу, верно? Если вывод разделен новыми строками, то как его обработать? Что мне не хватает?
Это событие запускается для обработки фрагментированных данных, поскольку размер буфера ограничен. В ваших данных уже должны быть новые строки, которые вы можете использовать для их построчной обработки.
Вот метод, который я использую для запуска процесса и получения его результатов и ошибок:
public static string ShellExecute(this string path, string command, TextWriter writer, params string[] arguments)
{
using (var process = Process.Start(new ProcessStartInfo { WorkingDirectory = path, FileName = command, Arguments = string.Join(" ", arguments), UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true }))
{
using (process.StandardOutput)
{
writer.WriteLine(process.StandardOutput.ReadToEnd());
}
using (process.StandardError)
{
writer.WriteLine(process.StandardError.ReadToEnd());
}
}
return path;
}
Например :
@"E:\Temp\MyWorkingDirectory".ShellExecute(@"C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\svcutil.exe", Console.Out);
Разве это не тупик? Документы MSDN говорят, что вы рискуете попасть в тупик, если одновременно будете прослушивать и вывод, и ошибку. Приложение остановится, если буфер ошибок заполнится, и дождется его опустошения. Но вы не очищаете буфер ошибок до тех пор, пока буфер вывода не будет завершен (чего не будет, поскольку приложение ожидает буфер ошибок) ...
Вот полный и простой код для этого. Это нормально работало, когда я его использовал.
var processStartInfo = new ProcessStartInfo
{
FileName = @"C:\SomeProgram",
Arguments = "Arguments",
RedirectStandardOutput = true,
UseShellExecute = false
};
var process = Process.Start(processStartInfo);
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
Обратите внимание, что это захватывает только стандартный выход; он не захватывает стандартный ошибка. Если вы хотите и то, и другое, используйте эта техника для каждого потока.
Это самый простой ответ
Перенаправление потока является асинхронным и потенциально будет продолжаться после завершения процесса. Умар упоминает об отмене после завершения процесса process.CancelOutputRead(). Однако это может привести к потере данных.
Это надежно работает для меня:
process.WaitForExit(...);
...
while (process.StandardOutput.EndOfStream == false)
{
Thread.Sleep(100);
}
Я не пробовал этот подход, но мне нравится предложение Слая:
if (process.WaitForExit(timeout))
{
process.WaitForExit();
}
При чтении потоков вывода / ошибок асинхронно вызывать process.WaitForExit () (версия без параметров) после того, как process.WaitForExit (тайм-аут) вернул true. Это будет блокироваться до тех пор, пока не будет достигнут конец потоков вывода / ошибок. Подробнее см. Примечания здесь: msdn.microsoft.com/en-us/library/ty0d8k56%28v=vs.110%29
Мне нужно было захватить как stdout, так и stderr и иметь тайм-аут, если процесс не завершился, как ожидалось. Я придумал это:
Process process = new Process();
StringBuilder outputStringBuilder = new StringBuilder();
try
{
process.StartInfo.FileName = exeFileName;
process.StartInfo.WorkingDirectory = args.ExeDirectory;
process.StartInfo.Arguments = args;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.EnableRaisingEvents = false;
process.OutputDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
process.ErrorDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
var processExited = process.WaitForExit(PROCESS_TIMEOUT);
if (processExited == false) // we timed out...
{
process.Kill();
throw new Exception("ERROR: Process took too long to finish");
}
else if (process.ExitCode != 0)
{
var output = outputStringBuilder.ToString();
var prefixMessage = "";
throw new Exception("Process exited with non-zero exit code of: " + process.ExitCode + Environment.NewLine +
"Output from process: " + outputStringBuilder.ToString());
}
}
finally
{
process.Close();
}
Я объединяю stdout и stderr в одну и ту же строку, но при необходимости вы можете сохранить их отдельно. Он использует события, поэтому должен обрабатывать их по мере их поступления (я считаю). Я успешно выполнил это и скоро буду тестировать его.
Это сработало для меня! Благодарность
Ответ от Иуды у меня не сработал (или не полный), так как приложение выходило после первого BeginOutputReadLine();
Это работает для меня как полный фрагмент, читающий постоянный вывод пинга:
var process = new Process();
process.StartInfo.FileName = "ping";
process.StartInfo.Arguments = "google.com -t";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.OutputDataReceived += (sender, a) => Console.WriteLine(a.Data);
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
Если вы не собираетесь взаимодействовать с приложением и заботитесь только о его выводе, вам не следует использовать для этого способ Async
BeginOutputReadLine()иStart(). Я обнаружил, что они не очень надежны и иногда могут обрезать начало вывода приложения.