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. Обратите внимание, что это также делает код НЕ зависает для меньших выходов (например, 3 КБ).
Возможно ли, что внутренний StandardOutput в ProcessStartInfo не может буферизовать 7MB? Если да, то что мне делать вместо этого? Если нет, что я делаю неправильно?
Ответы
Ответ 1
Проблема в том, что если вы перенаправляете 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.
}
}
}
Ответ 2
документация для 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();
Ответ 3
Ответ от Mark Byers превосходный, но я бы просто добавил следующее: делегаты OutputDataReceived и ErrorDataReceived необходимо удалить до того, как будут удалены функции outputWaitHandle и errorWaitHandle. Если процесс продолжает выводить данные после того, как таймаут был превышен, а затем завершен, к ним будут доступны выходные переменные outputWaitHandle и errorWaitHandle после того, как они будут удалены.
(FYI мне пришлось добавить это предостережение в качестве ответа, поскольку я не мог прокомментировать его сообщение.)
Ответ 4
Проблема с необработанным исключением 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 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);
}
}
}
Ответ 5
Это более современное ожидаемое решение на основе параллельной библиотеки задач (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!");
}
Implementation
реализации
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;
}
Ответ 6
Роб ответил и спас мне несколько часов испытаний. Прочитайте буфер вывода/ошибки перед ожиданием:
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
Ответ 7
У нас есть эта проблема (или вариант).
Попробуйте следующее:
1) Добавьте тайм-аут в p.WaitForExit(nnnn); где nnnn находится в миллисекундах.
2) Поместите вызов ReadToEnd перед вызовом WaitForExit. Это то, что мы видели в MS.
Ответ 8
Обратите внимание на решение Mark Byers:
![enter image description here]()
Ответ 9
Кредит EM0 для fooobar.com/questions/44191/...
Другие решения (в том числе EM0) все еще зашли в тупик для моего приложения из-за внутренних тайм-аутов и использования как стандартного, так и стандартногоError для созданного приложения. Вот что сработало для меня:
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();
Изменить: добавлена инициализация StartInfo для образца кода
Ответ 10
Я решил это так:
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, как для Windows 7, так и для Windows 8
Ответ 11
Я попытался создать класс, который бы разрешил вашу проблему, используя чтение асинхронного потока, принимая во внимание ответы Марка Байерса, Роба, stevejay. Сделав это, я понял, что есть ошибка, связанная с чтением выходного потока асинхронного процесса.
Я сообщил об ошибке в Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/3119134
Резюме:
Вы не можете этого сделать:
process.BeginOutputReadLine(); Process.Start();
Вы получите System.InvalidOperationException: StandardOut имеет не был перенаправлен или процесс еще не начался.
=============================================== ================================================== ===========================
Затем вам нужно запустить асинхронный вывод после того, как процесс начало:
Process.Start(); process.BeginOutputReadLine();
Сделав это, сделайте условие гонки, потому что выходной поток может принимать перед установкой асинхронности:
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();
=============================================== ================================================== ===========================
Тогда некоторые люди могли сказать, что вам просто нужно прочитать поток прежде чем вы установите его асинхронным. Но та же проблема возникает. Там будет состоянием гонки между синхронным чтением и установкой поток в асинхронный режим.
=============================================== ================================================== ===========================
Невозможно обеспечить безопасное асинхронное чтение выходного потока процесса на самом деле "Процесс" и "ProcessStartInfo" были разработаны.
Вероятно, вам лучше использовать асинхронное чтение, как это было предложено другими пользователями для вашего дела. Но вы должны знать, что вы можете пропустить некоторую информацию из-за состояния гонки.
Ответ 12
Ни один из вышеперечисленных ответов не выполняет эту работу.
Решение 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);
}
Этот код отлаживается и работает отлично.
Ответ 13
Введение
В настоящее время принятый ответ не работает (генерирует исключение), и существует слишком много обходных решений, но не полный код. Это, очевидно, тратит много времени людям, потому что это популярный вопрос.
Объединив ответ Марка Байера и Кароль Тил, я написал полный код, основанный на том, как я хочу использовать метод Process.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 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);
}
}
}
}
}
Ответ 14
Я знаю, что это ужин старый, но после прочтения всей этой страницы ни одно из решений не работало для меня, хотя я не пробовал Мухаммада Рехана, так как код был немного трудным для подражания, хотя я предполагаю, что он был на правильный трек. Когда я говорю, что это не сработало, что это не совсем так, иногда это будет работать нормально, я думаю, что это связано с длиной вывода до отметки 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);
}
}
}
Надеюсь, это поможет кому-то, кто думал, что это может быть так сложно!
Ответ 15
Прочитав все сообщения здесь, я остановился на консолидированном решении Marko Avlijaš. Однако это не решило все мои проблемы.
В нашей среде у нас есть служба Windows, в которой планируется запустить сотни различных файлов.bat.cmd.exe,... и т.д., Которые накопились за эти годы и были написаны разными людьми и в разных стилях. У нас нет контроля над написанием программ и скриптов, мы просто отвечаем за планирование, запуск и отчетность об успехе/неудаче.
Поэтому я почти полностью использовал все предложения с различными уровнями успеха. Ответ Марко был почти идеальным, но когда он запускался как служба, он не всегда захватывал stdout. Я никогда не понимал, почему нет.
Единственное решение, которое мы нашли во всех наших случаях: http://csharptest.net/319/using-the-processrunner-class/index.html
Ответ 16
Обходной путь, который я использовал, чтобы избежать всех сложностей:
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
Поэтому я создаю временный файл, перенаправляю на него выходные данные и ошибки, используя > outputfile > 2>&1
а затем просто читаю файл после завершения процесса.
Другие решения хороши для сценариев, когда вы хотите делать другие вещи с выводом, но для простых вещей это позволяет избежать большой сложности.
Ответ 17
Это сообщение может быть устаревшим, но я узнал основную причину, по которой он обычно зависает, из-за для redirectStandardoutput или если у вас есть redirectStandarderror.
Поскольку выходные данные или данные об ошибках велики, это вызовет время зависания, поскольку оно все еще обрабатывается на неопределенный срок.
чтобы решить эту проблему:
p.StartInfo.RedirectStandardoutput = False
p.StartInfo.RedirectStandarderror = False
Ответ 18
Мне кажется, что это простой и лучший подход (нам не нужно 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;
}
}
Ответ 19
Я думаю, что с асинхронным, возможно иметь более элегантное решение и не иметь взаимоблокировок даже при использовании и standardOutput, и 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.
}
}
Это основано на ответе Марка Байерса. Если вы не используете асинхронный метод, вы можете использовать string output = tStandardOutput.result;
вместо await
Ответ 20
У меня было то же самое замораживание при чтении результата с двоичным stdin/stdout, исправленным с помощью Flush() и Close() после записи в BaseStream (который на самом деле является Stream):
Program.process.StandardInput.BaseStream.Write(wavBytes, 0, wavBytes.Length);
Program.process.StandardInput.BaseStream.Flush();
Program.process.StandardInput.BaseStream.Close();
Ответ 21
Я прочитал много ответов и сделал свой. Не уверен, что это исправит в любом случае, но это исправляет в моей среде. Я просто не использую WaitForExit и использую WaitHandle.WaitAll для обоих выходных данных & сигналы об окончании ошибки. Я буду рад, если кто-то увидит возможные проблемы с этим. Или если это кому-то поможет. Для меня это лучше, потому что не использует таймауты.
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;
}
Ответ 22
Давайте назовем пример кода, размещенный здесь, перенаправителем, а другую программу - перенаправленной. Если бы это был я, я бы написал тестовую перенаправленную программу, которую можно использовать для дублирования проблемы.
Так я и сделал. Для тестовых данных я использовал спецификацию языка ECMA-334 С# v PDF; это около 5 МБ. Следующее является важной частью этого.
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;
}
Значение размера данных не соответствует фактическому размеру файла, но это не имеет значения. Неясно, всегда ли PDF файл использует CR и LF в конце строк, но это не имеет значения для этого. Вы можете использовать любой другой большой текстовый файл для тестирования.
Использование этого примера кода перенаправителя зависает, когда я пишу большой объем данных, но не когда я пишу небольшой объем.
Я очень старался как-то отследить выполнение этого кода, но не смог. Я закомментировал строки перенаправленной программы, которая отключила создание консоли для перенаправленной программы, чтобы попытаться получить отдельное окно консоли, но я не смог.
Потом я нашел, как запустить консольное приложение в новом окне, родительском окне или без окна. Таким образом, очевидно, что мы не можем (легко) иметь отдельную консоль, когда одна консольная программа запускает другую консольную программу без ShellExecute, и поскольку ShellExecute не поддерживает перенаправление, мы должны совместно использовать консоль, даже если мы не указываем окно для другого процесса.
Я предполагаю, что если перенаправленная программа заполняет буфер где-то, то она должна ждать, пока данные будут прочитаны, и если в этот момент данные не будут прочитаны перенаправителем, то это тупик.
Решение состоит в том, чтобы не использовать ReadToEnd и читать данные во время записи данных, но нет необходимости использовать асинхронное чтение. Решение может быть довольно простым. Следующее работает для меня с 5 МБ 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();
Другая возможность - использовать программу с графическим интерфейсом для перенаправления. Предыдущий код работает в приложении WPF, за исключением явных изменений.
Ответ 23
У меня была такая же проблема, но причина была другая. Однако это произойдет в Windows 8, но не под Windows 7. Кажется, что эта строка вызвала эту проблему.
pProcess.StartInfo.UseShellExecute = False
Решением было НЕ отключить UseShellExecute. Теперь я получил всплывающее окно Shell, которое нежелательно, но намного лучше, чем программа, ожидающая ничего особенного. Поэтому я добавил для этого следующее:
pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
Теперь меня беспокоит только то, почему это происходит в Windows 8.