Mam następujący kod:
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
Wiem, że dane wyjściowe z procesu, który zaczynam, mają około 7 MB. Uruchomienie go w konsoli Windows działa dobrze. Niestety programowo zawiesza się to na czas nieokreślony w WaitForExit. Zauważ również, że kod NIE zawiesza się dla mniejszych wyjść (jak 3KB).
Czy to możliwe, że wewnętrzny StandardOutput w ProcessStartInfo nie może buforować 7 MB? Jeśli tak, co powinienem zrobić zamiast tego? Jeśli nie, co robię źle?
c#
processstartinfo
Epaga
źródło
źródło
Odpowiedzi:
Problem polega na tym, że jeśli przekierujesz
StandardOutput
i / lubStandardError
wewnętrzny bufor może się zapełnić. Niezależnie od wybranej kolejności może wystąpić problem:StandardOutput
proces może zablokować próbę zapisu, więc proces nigdy się nie kończy.StandardOutput
użyciem ReadToEnd następnie Twój proces może blokować, jeśli proces nie zamyka sięStandardOutput
(na przykład, jeśli nigdy nie kończy, lub jeśli jest zablokowane do zapisuStandardError
).Rozwiązaniem jest użycie odczytów asynchronicznych, aby upewnić się, że bufor nie zostanie zapełniony. Aby uniknąć zakleszczenia i zebrać wszystkie dane wyjściowe z obu
StandardOutput
iStandardError
można to zrobić:EDYCJA: Zobacz odpowiedzi poniżej, aby dowiedzieć się, jak uniknąć wyjątku ObjectDisposedException w przypadku przekroczenia limitu czasu.
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. } } }
źródło
using
stwierdzenia dotyczące obsługi zdarzeń muszą być wyżej wusing
wypowiedzi dla samego procesu?Dokumentacja dla
Process.StandardOutput
mówi czytać zanim czekać inaczej można impasu, snippet przytoczone poniżej:// 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();
źródło
RedirectStandardOutput = true;
i nie używaszp.StandardOutput.ReadToEnd();
, otrzymujesz zakleszczenie / zawieszenie.Jest to bardziej nowoczesne i oczekiwane rozwiązanie oparte na bibliotece zadań równoległych (TPL) dla platformy .NET 4.5 i nowszych.
Przykład użycia
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!"); }
Realizacja
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 } }) { var cancellationTokenSource = timeout.HasValue ? new CancellationTokenSource(timeout.Value) : new CancellationTokenSource(); process.Start(); 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; }
źródło
Odpowiedź Marka Byersa jest doskonała, ale dodałbym tylko:
Delegaty
OutputDataReceived
iErrorDataReceived
muszą zostać usunięte przedoutputWaitHandle
ierrorWaitHandle
zostaną usunięte. Jeśli proces nadal wyprowadza dane po przekroczeniu limitu czasu, a następnie zakończy się, dostęp do zmiennychoutputWaitHandle
ierrorWaitHandle
zostanie uzyskany po ich usunięciu .(FYI musiałem dodać to zastrzeżenie jako odpowiedź, ponieważ nie mogłem skomentować jego postu.)
źródło
Problem z nieobsługiwanym ObjectDisposedException występuje, gdy upłynął limit czasu procesu. W takim przypadku pozostałe części warunku:
if (process.WaitForExit(timeout) && outputWaitHandle.WaitOne(timeout) && errorWaitHandle.WaitOne(timeout))
nie są wykonywane. Rozwiązałem ten problem w następujący sposób:
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); } } }
źródło
output
ierror
dooutputBuilder
? Czy ktoś może udzielić pełnej odpowiedzi, która działa?Rob odpowiedział na to i zaoszczędził mi kilka godzin prób. Przeczytaj bufor wyjściowy / błąd przed czekaniem:
// Read the output stream first and then wait. string output = p.StandardOutput.ReadToEnd(); p.WaitForExit();
źródło
WaitForExit()
?ReadToEnd
lub podobne metody (takie jakStandardOutput.BaseStream.CopyTo
) powrócą po przeczytaniu WSZYSTKICH danych. nic po nim nie nastąpiMamy też ten problem (lub wariant).
Spróbuj wykonać następujące czynności:
1) Dodaj limit czasu do p.WaitForExit (nnnn); gdzie nnnn jest wyrażone w milisekundach.
2) Umieść wywołanie ReadToEnd przed wywołaniem WaitForExit. To jest to, co widzieliśmy MS polecam.
źródło
Kredyt dla EM0 za https://stackoverflow.com/a/17600012/4151626
Inne rozwiązania (w tym EM0) nadal były zakleszczone dla mojej aplikacji z powodu wewnętrznych limitów czasu i użycia zarówno StandardOutput, jak i StandardError przez uruchomioną aplikację. Oto, co zadziałało dla mnie:
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();
Edycja: dodano inicjalizację StartInfo do przykładu kodu
źródło
Rozwiązałem to w ten sposób:
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();
Przekierowałem zarówno dane wejściowe, wyjściowe, jak i błędy oraz obsługiwałem odczyt ze strumieni wyjściowych i strumieni błędów. To rozwiązanie działa dla SDK 7- 8.1, zarówno dla Windows 7, jak i Windows 8
źródło
Próbowałem stworzyć klasę, która rozwiąże Twój problem za pomocą asynchronicznego odczytu strumienia, biorąc pod uwagę odpowiedzi Mark Byers, Rob, stevejay. Robiąc to, zdałem sobie sprawę, że jest błąd związany z odczytem strumienia wyjściowego procesu asynchronicznego.
Zgłosiłem ten błąd w Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/3119134
Podsumowanie:
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();
Prawdopodobnie lepiej będzie korzystać z odczytu asynchronicznego, jak sugerowali inni użytkownicy w Twoim przypadku. Ale powinieneś być świadomy, że możesz przegapić niektóre informacje z powodu stanu wyścigu.
źródło
Uważam, że jest to proste i lepsze podejście (nie potrzebujemy
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; } }
źródło
.FileName = Path + @"\ggsci.exe" + @" < obeycommand.txt"
się uprościć kodu? A może coś równoważnego,"echo command | " + Path + @"\ggsci.exe"
jeśli naprawdę nie chcesz używać oddzielnego pliku obeycommand.txt.Żadna z powyższych odpowiedzi nie działa.
Rozwiązanie Roba zawiesza się, a rozwiązanie „Mark Byers” otrzymuje usunięty wyjątek (wypróbowałem „rozwiązania” innych odpowiedzi).
Postanowiłem więc zasugerować inne rozwiązanie:
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); }
Ten kod został debugowany i działa doskonale.
źródło
GetProcessOutputWithTimeout
metody.Wprowadzenie
Obecnie zaakceptowana odpowiedź nie działa (zgłasza wyjątek) i istnieje zbyt wiele obejść, ale nie ma pełnego kodu. To oczywiście marnowanie czasu wielu ludzi, ponieważ jest to popularne pytanie.
Łącząc odpowiedź Marka Byersa i odpowiedź Karola Tyle'a, napisałem pełny kod w oparciu o to, jak chcę używać metody Process.Start.
Stosowanie
Użyłem go do stworzenia okna dialogowego postępu wokół poleceń git. Oto jak to wykorzystałem:
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; }
Teoretycznie można również łączyć stdout i stderr, ale nie testowałem tego.
Kod
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); } } } } }
źródło
Wiem, że to kolacja stara, ale po przeczytaniu całej tej strony żadne z rozwiązań nie działało dla mnie, chociaż nie próbowałem Muhammada Rehana, ponieważ kod był trochę trudny do naśladowania, chociaż myślę, że był na dobrej drodze . Kiedy mówię, że to nie zadziałało, to nie do końca prawda, czasami działało dobrze, myślę, że ma to coś wspólnego z długością wyjścia przed znakiem EOF.
W każdym razie rozwiązaniem, które zadziałało, było użycie różnych wątków do odczytywania StandardOutput i StandardError oraz do pisania wiadomości.
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); } } }
Mam nadzieję, że pomoże to komuś, kto pomyślał, że to może być takie trudne!
źródło
sw.FlushAsync(): Object is not set to an instance of an object. sw is null.
jak / gdzie należysw
zdefiniować?Po przeczytaniu wszystkich postów tutaj zdecydowałem się na skonsolidowane rozwiązanie Marko Avlijaša. Jednak nie rozwiązało to wszystkich moich problemów.
W naszym środowisku mamy usługę Windows, która ma uruchamiać setki różnych plików .bat .cmd .exe, ... itp., Które gromadziły się przez lata i zostały napisane przez wielu różnych ludzi i w różnych stylach. Nie mamy kontroli nad pisaniem programów i skryptów, jesteśmy tylko odpowiedzialni za planowanie, uruchamianie i raportowanie o sukcesach / niepowodzeniach.
Wypróbowałem więc prawie wszystkie sugestie tutaj z różnymi poziomami sukcesu. Odpowiedź Marko była prawie idealna, ale uruchamiany jako usługa nie zawsze przechwytywał standardowe wyjście. Nigdy nie doszedłem do sedna, dlaczego nie.
Jedyne znalezione przez nas rozwiązanie, które działa we WSZYSTKICH naszych przypadkach, to: http://csharptest.net/319/using-the-processrunner-class/index.html
źródło
Obejście, którego użyłem, aby uniknąć całej złożoności:
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
Więc tworzę plik tymczasowy, przekierowuję zarówno wyjście, jak i błąd do niego za pomocą,
> outputfile > 2>&1
a następnie po prostu czytam plik po zakończeniu procesu.Inne rozwiązania są dobre w scenariuszach, w których chcesz zrobić inne rzeczy z wyjściem, ale w przypadku prostych rzeczy pozwala to uniknąć dużej złożoności.
źródło
Przeczytałem wiele odpowiedzi i stworzyłem własne. Nie jestem pewien, czy to naprawi w każdym przypadku, ale naprawia się w moim środowisku. Po prostu nie używam WaitForExit i używam WaitHandle.WaitAll na obu sygnałach wyjściowych i końcowych błędów. Będzie mi miło, jeśli ktoś zobaczy z tym możliwe problemy. Albo jeśli to komuś pomoże. Dla mnie to lepsze, ponieważ nie używa timeoutów.
private static int DoProcess(string workingDir, string fileName, string arguments) { int exitCode; using (var process = new Process { StartInfo = { WorkingDirectory = workingDir, WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, UseShellExecute = false, FileName = fileName, Arguments = arguments, RedirectStandardError = true, RedirectStandardOutput = true }, EnableRaisingEvents = true }) { using (var outputWaitHandle = new AutoResetEvent(false)) using (var errorWaitHandle = new AutoResetEvent(false)) { process.OutputDataReceived += (sender, args) => { // ReSharper disable once AccessToDisposedClosure if (args.Data != null) Debug.Log(args.Data); else outputWaitHandle.Set(); }; process.ErrorDataReceived += (sender, args) => { // ReSharper disable once AccessToDisposedClosure if (args.Data != null) Debug.LogError(args.Data); else errorWaitHandle.Set(); }; process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); WaitHandle.WaitAll(new WaitHandle[] { outputWaitHandle, errorWaitHandle }); exitCode = process.ExitCode; } } return exitCode; }
źródło
Myślę, że dzięki async można mieć bardziej eleganckie rozwiązanie i nie mieć zakleszczeń, nawet jeśli używa się zarówno standardOutput, jak i 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. } }
Opiera się na odpowiedzi Marka Byersa. Jeśli nie korzystasz z metody asynchronicznej, możesz użyć
string output = tStandardOutput.result;
zamiastawait
źródło
Żadna z tych odpowiedzi nie pomogła mi, ale to rozwiązanie działało dobrze z obsługą zawieszeń
https://stackoverflow.com/a/60355879/10522960
źródło
Ten post może być nieaktualny, ale odkryłem, że główną przyczyną, dla której zwykle się zawiesza, jest przepełnienie stosu dla wyjścia redirectStandardoutput lub błąd redirectStandarderror.
Ponieważ dane wyjściowe lub dane o błędach są duże, spowoduje to zawieszenie, ponieważ nadal przetwarza przez nieokreślony czas.
aby rozwiązać ten problem:
źródło
Nazwijmy zamieszczony tutaj przykładowy kod redirectorem, a drugi program przekierowanym. Gdybym to był ja, prawdopodobnie napisałbym program przekierowany testowo, który można wykorzystać do powielenia problemu.
Więc zrobiłem. Do danych testowych użyłem specyfikacji języka ECMA-334 C # v PDF; jest to około 5 MB. Poniżej znajduje się ważna część tego.
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; }
Wartość rozmiaru danych nie odpowiada rzeczywistemu rozmiarowi pliku, ale to nie ma znaczenia. Nie jest jasne, czy plik PDF zawsze używa na końcu linii zarówno CR, jak i LF, ale nie ma to znaczenia. Możesz użyć dowolnego innego dużego pliku tekstowego do przetestowania.
Korzystając z tego, przykładowy kod readresatora zawiesza się, gdy piszę dużą ilość danych, ale nie podczas pisania małej ilości.
Bardzo starałem się jakoś prześledzić wykonanie tego kodu i nie mogłem. Skomentowałem wiersze przekierowanego programu, który wyłączał tworzenie konsoli dla przekierowanego programu, aby spróbować uzyskać oddzielne okno konsoli, ale nie mogłem.
Potem znalazłem Jak uruchomić aplikację konsolową w nowym oknie, w oknie rodzica lub bez okna . Tak więc najwyraźniej nie możemy (łatwo) mieć oddzielnej konsoli, gdy jeden program konsoli uruchamia inny program konsoli bez ShellExecute, a ponieważ ShellExecute nie obsługuje przekierowywania, musimy udostępniać konsolę, nawet jeśli nie określimy okna dla drugiego procesu.
Zakładam, że jeśli przekierowany program zapełnia gdzieś bufor, to musi czekać na odczytanie danych i jeśli w tym momencie żadne dane nie są odczytywane przez readresator, to jest to zakleszczenie.
Rozwiązaniem jest nieużywanie ReadToEnd i odczytywanie danych podczas ich zapisywania, ale nie jest konieczne stosowanie odczytów asynchronicznych. Rozwiązanie może być całkiem proste. Poniższe działa dla mnie z 5 MB PDF.
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();
Inną możliwością jest użycie programu GUI do wykonania przekierowania. Poprzedni kod działa w aplikacji WPF z wyjątkiem oczywistych modyfikacji.
źródło
Miałem ten sam problem, ale powód był inny. Może się to jednak zdarzyć pod Windows 8, ale nie pod Windows 7. Wydaje się, że problem spowodował następujący wiersz.
Rozwiązaniem było NIE wyłączenie UseShellExecute. Otrzymałem teraz wyskakujące okienko Shell, które jest niepożądane, ale znacznie lepsze niż program czekający na nic szczególnego. Dlatego dodałem następujące obejście:
Teraz jedyne, co mnie niepokoi, to dlaczego tak się dzieje w systemie Windows 8 w pierwszej kolejności.
źródło
UseShellExecute
ustawić wartość false, jeśli chcesz przekierować dane wyjściowe.