StandardOutput.ReadToEnd() зависает
У меня есть программа, которая часто использует внешнюю программу и считывает ее выходы.
Он работает очень хорошо, используя ваш обычный процесс перенаправления вывода, но один конкретный аргумент по какой-то причине зависает, когда я пытаюсь его прочитать, никакое сообщение об ошибке - исключение, оно просто "останавливается", когда оно достигает этой строки.
Я, конечно, использую централизованную функцию для вызова и чтения вывода из программы, которая заключается в следующем:
public string ADBShell(string adbInput)
{
try
{
//Create Empty values
string result = string.Empty;
string error = string.Empty;
string output = string.Empty;
System.Diagnostics.ProcessStartInfo procStartInfo
= new System.Diagnostics.ProcessStartInfo(toolPath + "adb.exe");
procStartInfo.Arguments = adbInput;
procStartInfo.RedirectStandardOutput = true;
procStartInfo.RedirectStandardError = true;
procStartInfo.UseShellExecute = false;
procStartInfo.CreateNoWindow = true;
procStartInfo.WorkingDirectory = toolPath;
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procStartInfo;
proc.Start();
// Get the output into a string
proc.WaitForExit();
result = proc.StandardOutput.ReadToEnd();
error = proc.StandardError.ReadToEnd(); //Some ADB outputs use this
if (result.Length > 1)
{
output += result;
}
if (error.Length > 1)
{
output += error;
}
Return output;
}
catch (Exception objException)
{
throw objException;
}
}
Линия, которая висит, result = proc.StandardOutput.ReadToEnd();
, но опять же, не каждый раз, только при отправке определенного аргумента ( "start-server" ). Все остальные аргументы работают очень хорошо - он считывает значение и возвращает его.
Это также странно, как он висит. Он не замерзает или не дает ошибку или что-то еще, он просто прекращает обработку. Как будто это была команда "return", за исключением того, что она даже не возвращается к вызывающей функции, она просто останавливает все, пока все еще работает и работает интерфейс.
Кто-нибудь испытал это раньше? Кто-нибудь знает, что я должен попробовать? Я предполагаю, что это что-то неожиданное в самом потоке, но есть ли способ, которым я могу обрабатывать/игнорировать это, чтобы он все равно читал?
Ответы
Ответ 1
Предлагаемые решения с BeginOutputReadLine()
являются хорошим способом, но в таких ситуациях это неприменимо, потому что процесс (конечно, с использованием WaitForExit()
) завершается раньше, чем выход async завершен полностью.
Итак, я попытался реализовать его синхронно и обнаружил, что решение использует метод Peek()
из класса StreamReader
. Я добавил проверку на Peek() > -1
, чтобы убедиться, что это не конец потока, как описано в статья MSDN и наконец, он работает и перестает вилять!
Вот код:
var process = new Process();
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.WorkingDirectory = @"C:\test\";
process.StartInfo.FileName = "test.exe";
process.StartInfo.Arguments = "your arguments here";
process.Start();
var output = new List<string>();
while (process.StandardOutput.Peek() > -1)
{
output.Add(process.StandardOutput.ReadLine());
}
while (process.StandardError.Peek() > -1)
{
output.Add(process.StandardError.ReadLine());
}
process.WaitForExit();
Ответ 2
Проблема заключается в том, что вы используете синхронные методы ReadToEnd
для потоков StandardOutput
и StandardError
. Это может привести к потенциальному тупику, который вы испытываете. Это даже описано в MSDN. Там описано решение. В основном это: использовать асинхронную версию BeginOutputReadLine
для чтения данных потока StandardOutput
:
p.BeginOutputReadLine();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
Реализация чтения Async с использованием BeginOutputReadLine см. в ProcessStartInfo, зависающем на "WaitForExit" ? Почему?
Ответ 3
У меня была такая же проблема взаимоблокировки. Этот фрагмент кода работал у меня.
ProcessStartInfo startInfo = new ProcessStartInfo("cmd")
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true
};
Process process = new Process();
process.StartInfo = startInfo;
process.Start();
process.StandardInput.WriteLine("echo hi");
process.StandardInput.WriteLine("exit");
var output = process.StandardOutput.ReadToEnd();
process.Dispose();
Ответ 4
Что-то вроде:
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.OutputDataReceived += (sender, args) =>
{
var outputData = args.Data;
// ...
};
process.ErrorDataReceived += (sender, args) =>
{
var errorData = args.Data;
// ...
};
process.WaitForExit();
Ответ 5
У меня была такая же проблема, что ошибка просто висела.
На основании вашего ответа на Daniel Hilgarth я даже не пытался использовать эти коды, хотя я думаю, что они бы сработали для меня.
Так как я хочу иметь возможность сделать какой-то дополнительный результат, в конце концов, я решил, что сделаю это с обоими выходами, выполненными в фоновом потоке.
public static class RunCommands
{
#region Outputs Property
private static object _outputsLockObject;
private static object OutputsLockObject
{
get
{
if (_outputsLockObject == null)
Interlocked.CompareExchange(ref _outputsLockObject, new object(), null);
return _outputsLockObject;
}
}
private static Dictionary<object, CommandOutput> _outputs;
private static Dictionary<object, CommandOutput> Outputs
{
get
{
if (_outputs != null)
return _outputs;
lock (OutputsLockObject)
{
_outputs = new Dictionary<object, CommandOutput>();
}
return _outputs;
}
}
#endregion
public static string GetCommandOutputSimple(ProcessStartInfo info, bool returnErrorIfPopulated = true)
{
// Redirect the output stream of the child process.
info.UseShellExecute = false;
info.CreateNoWindow = true;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
var process = new Process();
process.StartInfo = info;
process.ErrorDataReceived += ErrorDataHandler;
process.OutputDataReceived += OutputDataHandler;
var output = new CommandOutput();
Outputs.Add(process, output);
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
// Wait for the process to finish reading from error and output before it is finished
process.WaitForExit();
Outputs.Remove(process);
if (returnErrorIfPopulated && (!String.IsNullOrWhiteSpace(output.Error)))
{
return output.Error.TrimEnd('\n');
}
return output.Output.TrimEnd('\n');
}
private static void ErrorDataHandler(object sendingProcess, DataReceivedEventArgs errLine)
{
if (errLine.Data == null)
return;
if (!Outputs.ContainsKey(sendingProcess))
return;
var commandOutput = Outputs[sendingProcess];
commandOutput.Error = commandOutput.Error + errLine.Data + "\n";
}
private static void OutputDataHandler(object sendingProcess, DataReceivedEventArgs outputLine)
{
if (outputLine.Data == null)
return;
if (!Outputs.ContainsKey(sendingProcess))
return;
var commandOutput = Outputs[sendingProcess];
commandOutput.Output = commandOutput.Output + outputLine.Data + "\n";
}
}
public class CommandOutput
{
public string Error { get; set; }
public string Output { get; set; }
public CommandOutput()
{
Error = "";
Output = "";
}
}
Это сработало для меня и позволило мне не использовать таймаут для чтения.
Ответ 6
Что-то элегантное и работающее для меня:
Process nslookup = new Process()
{
StartInfo = new ProcessStartInfo("nslookup")
{
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
}
};
nslookup.Start();
nslookup.StandardInput.WriteLine("set type=srv");
nslookup.StandardInput.WriteLine("_ldap._tcp.domain.local");
nslookup.StandardInput.Flush();
nslookup.StandardInput.Close();
string output = nslookup.StandardOutput.ReadToEnd();
nslookup.WaitForExit();
nslookup.Close();
Этот ответ я нашел здесь, и трюк использует Flush()
и Close()
для стандартного ввода.
Ответ 7
Принятое решение для ответа не сработало для меня. Мне пришлось использовать задачи, чтобы избежать тупика:
//Code to start process here
String outputResult = GetStreamOutput(process.StandardOutput);
String errorResult = GetStreamOutput(process.StandardError);
process.WaitForExit();
С функцией GetStreamOutput
следующим образом:
private string GetStreamOutput(StreamReader stream)
{
//Read output in separate task to avoid deadlocks
var outputReadTask = Task.Run(() => stream.ReadToEnd());
return outputReadTask.Result;
}
Ответ 8
На всякий случай кто-то натыкается на этот вопрос, когда он хочет использовать Windows Forms и TextBox
(или RichTextBox
), чтобы отображать ошибки и выводит процесс в реальном времени (поскольку они записываются в process.StandardOutput
/process.StandardError
).
Вам нужно использовать OutputDataReceived()
/ErrorDataReceived()
, чтобы читать оба потока без взаимоблокировок, но, насколько мне известно, нет пути (насколько я знаю), чтобы избежать взаимоблокировок, даже ответа Федора, который теперь содержит ответ "Ответ", тег и самый любимый на сегодняшний день, не делает трюк для меня.
Однако, когда вы используете RichTextBox (или TextBox) для вывода данных, другая проблема, с которой вы сталкиваетесь, заключается в том, как фактически записывать данные в текстовое поле в режиме реального времени (после его поступления). Вы получаете доступ к данным внутри одного из фоновых потоков OutputDataReceived()
/ErrorDataReceived()
, и вы можете только AppendText()
из основного потока.
То, что я впервые попытался сделать, вызвал process.Start()
из фонового потока, а затем вызвал BeginInvoke() => AppendText()
в OutputDataReceived()
/ErrorDataReceived()
потоки, тогда как основной поток был process.WaitForExit()
.
Однако это привело к замораживанию моей формы и, в конечном счете, к вечности. После нескольких дней попыток я закончил с приведенным ниже решением, которое, похоже, работает очень хорошо.
Вкратце, вам нужно добавить сообщения в параллельную коллекцию внутри потоков OutputDataReceived()
/ErrorDataReceived()
, в то время как основной поток должен постоянно пытаться извлекать сообщения из этой коллекции и добавлять их в текстовое поле:
ProcessStartInfo startInfo
= new ProcessStartInfo(File, mysqldumpCommand);
process.StartInfo.FileName = File;
process.StartInfo.Arguments = mysqldumpCommand;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.RedirectStandardInput = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
process.EnableRaisingEvents = true;
ConcurrentQueue<string> messages = new ConcurrentQueue<string>();
process.ErrorDataReceived += (object se, DataReceivedEventArgs ar) =>
{
string data = ar.Data;
if (!string.IsNullOrWhiteSpace(data))
messages.Enqueue(data);
};
process.OutputDataReceived += (object se, DataReceivedEventArgs ar) =>
{
string data = ar.Data;
if (!string.IsNullOrWhiteSpace(data))
messages.Enqueue(data);
};
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
while (!process.HasExited)
{
string data = null;
if (messages.TryDequeue(out data))
UpdateOutputText(data, tbOutput);
Thread.Sleep(5);
}
process.WaitForExit();
Единственным недостатком этого подхода является тот факт, что вы можете потерять сообщения в довольно редком случае, когда процесс начинает писать их между process.Start()
и process.BeginErrorReadLine()
/process.BeginOutputReadLine()
, просто имейте это в виду. Единственный способ избежать этого - прочитать полные потоки и (или) получить доступ к ним только при завершении процесса.
Ответ 9
первый
// 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();
второй
// Do not perform a synchronous read to the end of both
// redirected streams.
// string output = p.StandardOutput.ReadToEnd();
// string error = p.StandardError.ReadToEnd();
// p.WaitForExit();
// Use asynchronous read operations on at least one of the streams.
p.BeginOutputReadLine();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
Это от MSDN