ProcessStartInfo висит на "WaitForExit"? Зачем?
У меня есть следующий код:
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. Примечание также это код не висит для небольших выходов (например, 3KB).
возможно ли, что внутренний стандартный вывод в ProcessStartInfo не может буферизовать 7 МБ? Если да, то что мне делать вместо этого? Если нет, то что я делаю не так?
17 ответов:
проблема в том, что если перенаправить
StandardOutput
и/илиStandardError
внутренний буфер может заполниться. Какой бы порядок вы ни использовали, может возникнуть проблема:
- если вы ждете завершения процесса перед чтением
StandardOutput
процесс может блокировать попытки записи в него, поэтому процесс никогда не заканчивается.- если Вы читаете
StandardOutput
используя ReadToEnd тогда код процесс может блокировать, если процесс никогда не закрываетсяStandardOutput
(например, если он никогда не заканчивается, или если заблокирована запись вStandardError
).решение заключается в использовании асинхронного чтения, чтобы гарантировать, что буфер не заполняется. Чтобы избежать каких-либо тупиков и собрать все выходные данные из обоих
StandardOutput
иStandardError
можно сделать так:EDIT: смотрите ответы ниже, Как избежать 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. } } }
The документация на
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 должны быть удалены до того, как outputWaitHandle и errorWaitHandle будут удалены. Если процесс продолжает выводить данные после превышения времени ожидания и затем завершается, переменные outputWaitHandle и errorWaitHandle будут доступны после удаления.
(к вашему сведению, я должен был добавить это предостережение в качестве ответа, поскольку я не мог прокомментировать его сообщение.)
проблема с необработанным 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 = 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 { outputBuilder.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); } } }
Роб ответил на него и сэкономил мне еще несколько часов испытаний. Прочитайте буфер вывода / ошибок перед ожиданием:
// Read the output stream first and then wait. string output = p.StandardOutput.ReadToEnd(); p.WaitForExit();
У нас тоже есть эта проблема (или вариант).
попробуйте следующее:
1) добавьте тайм-аут к p. WaitForExit (nnnn); где nnnn находится в миллисекундах.
2) Поместите вызов ReadToEnd перед вызовом WaitForExit. Это и что мы видели MS рекомендуем.
это более современное ожидаемое решение на основе параллельной библиотеки задач (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; }
кредит EM0 для 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();
Edit: добавлена инициализация 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
Я попытался создать класс, который решил бы вашу проблему с помощью асинхронного чтения потока, принимая во внимание ответы Mark Byers, Rob, stevejay. При этом я понял, что есть ошибка, связанная с асинхронным процессом вывода потока чтения.
Я сообщил, что ошибка в Microsoft:https://connect.microsoft.com/VisualStudio/feedback/details/3119134
резюме:
вы не можете сделать это:
ни один из ответов выше не выполняет эту работу.
решение Rob зависает, и решение "Mark Byers" получает удаленное исключение.(Я попробовал "решения" других ответов).
поэтому я решил предложить другое решение:
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); }
этот код отлажен и прекрасно работает.
введение
В настоящее время принятый ответ не работает (исключение бросков), и есть слишком много обходных путей, но нет полного кода. Это, очевидно, тратить много времени, потому что это популярный вопрос.
комбинируя ответ Марка Байерса и ответ Кароля Тила, я написал полный код, основанный на том, как я хочу использовать этот процесс.метод 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, которая выполняется сотни различных .летучая мышь.УМК. исполняемый.,.. так далее. файлы, которые накапливались на протяжении многих лет и были написаны многими разными людьми и в разных стилях. Мы не имеем никакого контроля над написанием программ и скриптов, мы просто несем ответственность за планирование, запуск и отчетность об успехе/неудаче.
поэтому я попробовал почти все предложения здесь с разными уровнями успеха. Ответ Марко был почти идеальным, но при запуске в качестве службы он не всегда захватывал stdout. Я так и не понял, почему нет.
единственное решение, которое мы нашли, что работает во всех наших случаях это:http://csharptest.net/319/using-the-processrunner-class/index.html
этот пост может быть устаревшим, но я узнал основную причину, почему он обычно зависает из-за переполнения стека для redirectStandardoutput или если у вас есть redirectStandarderror.
поскольку выходные данные или данные об ошибках велики, это вызовет время зависания, поскольку оно все еще обрабатывается в течение неопределенной продолжительности.
Итак, чтобы решить эту проблему:
p.StartInfo.RedirectStandardoutput = False p.StartInfo.RedirectStandarderror = False
Я думаю, что это простой и лучший подход (нам не нужны
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; } }
у меня была та же проблема, но причина была в другом. Однако это произойдет под Windows 8, но не под Windows 7. Следующая строка, похоже, вызвала проблему.
pProcess.StartInfo.UseShellExecute = False
решение состояло в том, чтобы не отключать UseShellExecute. Теперь я получил всплывающее окно оболочки, которое нежелательно, но намного лучше, чем программа, ожидающая ничего особенного. Поэтому я добавил следующую работу для этого:
pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
теперь единственное, что беспокоит меня это, почему это происходит под Windows 8, в первую очередь.