Последовательное считывание из потока вывода и потока ошибок при работе с CMD (C#)
Разрабатываю модуль для приложения, позволяющий работать с командными строками. Тестируюсь на CMD. Задача - позволить пользователю ввести пул команд для выполнения и получать результат по каждой из них, причем что всё выполнение должно находиться в одном сеансе консоли и результат по каждой из команд должен быть получен сразу после выполнения команды для возможности его использования в последующих командах.
Проблема в следующем - я где-то нашла классный метод получения ответа на команду, который здорово справляется с потоком output. Им же я пытаюсь получать поток error, но почему-то оно не работает так, как хотелось бы...
Если ошибочная команда идет первая - то все читается отлично. Если ошибочная команда идет после другой ошибочной команды, и если не читать буфер первой команды, то у второй ошибка считается. Если же считать у обеих - ничего не считается. Задача чтения (readAsync) висит якобы невыполненной. Если ошибочная команда одна и она где-то в середине других - ошибка снова не считается.
Вот основной метод обработки команд:
var process = Process.Start(new ProcessStartInfo
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardInput = true,
RedirectStandardError = true,
FileName = "cmd",
StandardOutputEncoding = enc,
StandardErrorEncoding = enc
});
List<string> commands = new List<string>() { "ipconfig", "node -b","cd 123" };
List<string> results = new List<string>();
List<string> resultsErr = new List<string>();
using (StreamWriter sr = process.StandardInput)
{
for (int i = 0; i < commands.Count; i++)
{
if (sr.BaseStream.CanWrite)
{
sr.WriteLine((string)null); // для того, чтобы каждая команда начиналась с новой строки и можно было обрезать результат и выдавать его без лишних строк
sr.WriteLine(commands[i]);
}
var result = read(process, 1);
result = takeOnlyAnswer(result, commands[i]);
results.Add(result);
//тут где-то нужно придумать флаги? (читать или не читать)
var resultErr = read(process, 1, true);
resultErr = resultErr.TrimEnd('\r');
resultsErr.Add(resultErr);
}
}
label1.Text = String.Join("\n", results);
label2.Text = String.Join("\n", resultsErr);
и собственно метод чтения, честно стыренный с просторов интернета и чуть-чуть доработанный под свои нужны
private string read(Process process, int secondsWait = 1, bool err = false)
{
char[] buffer = new char[256];
string output = "";
Task<int> read = null;
int countWait = secondsWait * 10;
int count = 0;
int countErr = 0;
while (true)
{
while (read == null && countErr != countWait)
try
{
if (!err)
read = process.StandardOutput.ReadAsync(buffer, 0, buffer.Length);
else
read = process.StandardError.ReadAsync(buffer, 0, buffer.Length);
}
catch
{
countErr++;
Thread.Sleep(100);
}
countErr = 0;
if (read == null)
break;
read.Wait(100); // an arbitray timeout
if (read.IsCompleted)
{
if (read.Result > 0)
{
output += new string(buffer, 0, read.Result);
read = null;
count = 0;
continue;
}
break;
}
else if (read.Status == TaskStatus.WaitingForActivation && count == countWait)
{
break;
}
count++;
}
return output;
}
Метод takeOnlyAnswer просто обрезает лишние строки, оставляя чисто ответ от сообщения. Не очень важен.
Ошибка, наверное, с самим пониманием побайтового чтения... Всю голову сломала, надеюсь на вашу помощь!
Ответы (1 шт):
Думаю, проблема в следующем.
Ваш код
var result = read(process, 1);
// ...
var resultErr = read(process, 1, true);
сначала читает из stdout, а затем из stderr. Если программа, которую вы запускаете, сначала пишет в stderr, что происходит?
- Вы пытаетесть читать из stdout, пока в stdout ничего нету, ваш код ждёт.
- Программа пишет в stderr. Поскольку запись происходит в pipe, то она (запись) идёт до исчерпания буфера, а потом останавливается, ожидая, что читающий процесс прочитает записанные байты.
- Ваш код не читает ничего из stderr, т. к. вы ждёте, пока что-то появится в stdout. Но stdout будет закрыт лишь в конце работы программы, то есть после того, как отработает заблокированная запись в stderr.
Вот вам и deadlock.
Более простой код, который читает у процесса как stdout, так и stderr, можно найти прямо в документации:
string stderr = null;
process.ErrorDataReceived += new DataReceivedEventHandler((sender, e) =>
{ stderr += e.Data; });
process.Start();
// To avoid deadlocks, use an asynchronous read operation on at least one of the streams.
process.BeginErrorReadLine();
string stdout = process.StandardOutput.ReadToEnd();
process.WaitForExit();
В конце работы в переменных stdout и stderr будет то, что вам нужно.