Захват вывода процесса синхронно (т.е. "Когда это произойдет" )
Я пытаюсь запустить процесс и захватить вывод, пришел далеко, но я не совсем в том решении, которое я бы хотел.
В частности, я пытаюсь использовать reset IIS на своей машине разработки из небольшого приложения-приложения, которое я пишу. Я пришел к выводу, экспериментируя, что безопасный способ сделать это - запустить iisreset.exe
в дочернем процессе.
Если вы запустите iisreset.exe
в командной строке, вы получите обратную связь во время процесса. Запуск iisreset занимает несколько секунд, и генерируется несколько строк обратной связи с паузами между ними.
Я хотел бы зафиксировать эту обратную связь и представить ее в своем приложении Windows Forms (в ListBox
), и мне это удалось. Моя оставшаяся забота заключается в том, что я не получаю ее до окончания процесса ребенка. Я хотел бы получить выход из дочернего процесса, по очереди, сразу же, когда строки создаются.
Я пытался сделать домашнее задание, читая/тестируя вещи, например. это:
и еще несколько с похожим контентом. Большинство (все?) Получают выход асинхронно (например, с помощью Process.ReadToEnd()
). Я хочу синхронизировать вывод, который согласно документации MSDN включает в себя создание обработчика событий и т.д., И я пробовал это. Он работает, но обработчик события не вызывается, пока процесс не завершится. Я получаю вывод от iisreset.exe, но не до конца.
Чтобы исключить возможность того, что это имеет какое-то отношение к iisreset.exe, я написал небольшое консольное приложение, которое генерирует некоторый вывод, останавливаясь между ними:
namespace OutputGenerator
{
class Program
{
static void Main(string[] args)
{
System.Console.WriteLine("OutputGenerator starting and pausing for 10 seconds..");
System.Threading.Thread.Sleep(10000);
System.Console.WriteLine("Pausing for another 10 seconds..");
System.Threading.Thread.Sleep(10000);
System.Console.WriteLine("Exiting!");
}
}
}
Тестирование с этим оказывается, что я получаю захваченные данные, когда захочу. Таким образом, до некоторой степени кажется, что способ, которым iisreset.exe выводит данные, вступает в игру здесь.
Вот код программы (приложение Windows Forms), которая выполняет захват:
using System;
using System.Windows.Forms;
using System.Diagnostics;
namespace OutputCapturer
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnRun_Click(object sender, EventArgs e)
{
// Running this will show all output after the process has exited
//String path = @"C:\Windows\system32\iisreset.exe";
// Running this will show all output "when it happens"
String path = @"C:\OutputGenerator.exe";
var p = new Process();
p.StartInfo.FileName = path;
p.StartInfo.UseShellExecute = false; // ShellExecute = true not allowed when output is redirected..
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.CreateNoWindow = true;
p.OutputDataReceived += OutputDataReceived;
p.Start();
p.BeginOutputReadLine();
}
private delegate void OutputDataToTextboxDelegate(String s);
void OutputDataToTextbox(String s)
{
tbxOutput.Text += s + Environment.NewLine;
tbxOutput.Refresh();
}
private void OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null && e.Data.ToString() != "")
{
// Must run the update of the textbox in the same thread that created it..
tbxOutput.Invoke(
new OutputDataToTextboxDelegate(OutputDataToTextbox),
DateTime.Now.ToString() + ": " + e.Data.ToString()
);
}
}
}
}
Думая, что это была проблема с EOL-кодированием (вывод iisreset.exe
, представляющий собой одну строку для моего приложения)), я запустил сеанс отладки. Неа. Обработчик событий для StandardOutput
вызывается несколько раз (один раз для каждой выходной строки из iisreset.exe
), но эти вызовы поступают в один пакет после завершения процесса.
Я бы ЛЮБЛЮ, если бы смог получить результат от iisreset.exe
", когда это произойдет", чтобы я мог показать его как индикацию прогресса.
Я видел еще один поток с той же/подобной проблемой, Асинхронный захват с выхода процесса не работает должным образом, но без решения.
Я как бы тупой.
Ответы
Ответ 1
В моей конкретной ситуации решение - это то, что предложил г-н Моисей в комментарии выше, то есть запустите iisreset /stop
, а затем iisreset /start
.
Мне нужен правильный ответ, а не комментарий, чтобы отметить его как "принятый ответ", поэтому этот ответ скорее административен, чем новый. Кредит должен пойти к господину Моисею..: -)
Ответ 2
Чтобы выполнить автоматическую очистку printfs/stdouts
C эквивалент autoflush (flush stdout после каждой записи)?
Это спасло мою задницу...
Ответ 3
Кажется, что sixlettervariables верна, и что это имеет какое-то отношение к iisreset.exe
, это не очистка буферов для каждой строки. (Я все еще удивляюсь, что заставляет его работать в простой командной строке - то есть что делает cmd.exe
?)
Во всяком случае.. Я пробовал то, что предложил apacay, и написал это:
private void btnRun_Click(object sender, EventArgs e)
{
// Running this will show the output after the process has finished
//String path = @"C:\Windows\system32\iisreset.exe";
// Running this will show all output "when it happens"
String path = @"C:\OutputGenerator.exe";
var p = new Process();
p.StartInfo.FileName = path;
p.StartInfo.UseShellExecute = false; // ShellExecute = true not allowed when output is redirected..
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.CreateNoWindow = true;
p.Start();
StreamReader sr = p.StandardOutput;
while (!sr.EndOfStream)
{
String s = sr.ReadLine();
if (s != "")
{
tbxOutput.Text += DateTime.Now.ToString() + ": " + s + Environment.NewLine;
}
tbxOutput.Refresh();
}
}
Обратите внимание, что я использую timestamping, когда получаю каждую строку. Для моего OutputGenerator я получаю следующее:
2011-07-06 17:49:11: OutputGenerator starting and pausing for 10 seconds..
2011-07-06 17:49:21: Pausing for another 10 seconds..
2011-07-06 17:49:31: Exiting!
И для iisreset.exe я получаю следующее:
2011-07-06 17:57:11: Attempting stop...
2011-07-06 17:57:11: Internet services successfully stopped
2011-07-06 17:57:11: Attempting start...
2011-07-06 17:57:11: Internet services successfully restarted
Запуск iisreset.exe в командной строке, эти строки имеют паузы между ними, в течение промежутка времени, равного 10 секундам.
Случай кажется более или менее закрытым сейчас. Не то чтобы я все доволен, но я нахожусь на дороге, кажется. Я с неохотой буду жить с ним.
Подводя итог: В общем случае вполне можно фиксировать вывод синхронно с тем, когда он сгенерирован. Этот поток представляет код для двух способов сделать это - путем создания обработчика событий и путем "опроса" потока. В моем конкретном случае есть что-то с тем, как iisreset.exe
генерирует вывод, который предотвращает это.
Спасибо тем, кто участвовал и внес свой вклад!
Ответ 4
Хорошо... ты мог бы пинать старую школу. Вывод может быть перенаправлен на вход другой программы с использованием команд DOS старой школы (foo.exe | bar.exe
). Напишите программу, которая читает со стандартного ввода, и вы будете получать ее каждый раз, когда поток сбрасывается.
Edit
Вы также можете перенаправить вывод на именованный канал и прочитать его. Это также будет "как это бывает".
Ответ 5
У меня была эта проблема, и мне пришлось ее решать, когда мои журналы были слишком длинными для чтения в одном файле readtoend.
Это то, что я сделал для его решения. Пока это хорошо.
myProcess.StartInfo.FileName = path;
myProcess.StartInfo.Arguments = args;
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.ErrorDialog = false;
myProcess.StartInfo.CreateNoWindow = true;
myProcess.StartInfo.RedirectStandardError = true;
myProcess.StartInfo.RedirectStandardInput = (stdIn != null);
myProcess.StartInfo.RedirectStandardOutput = true;
myProcess.Start();
int index;
OpenLogFile(myLog); //LOGGGGGGGGGGGGG
if (myProcess.StartInfo.RedirectStandardInput)
{
StreamWriter sw = myProcess.StandardInput;
sw.Write(stdIn + Convert.ToChar(26));
}
StreamReader sr = myProcess.StandardOutput;
/*stdOut = new ArrayLi
*/
while (!sr.EndOfStream)
{ //LOGGGGGGGGGGGGG
Log(sr.ReadLine(), true);
}
Здесь OpenLogFile
private void OpenLogFile(string fileName)
{
if (file == StreamWriter.Null)
{
file = new StreamWriter(fileName, true);
file.AutoFlush = true;
}
}
Конечно, Log - это функция, которая что-то делает в другом месте. Но решение вашего вопроса лежит здесь:
while (!sr.EndOfStream)
{ //LOGGGGGGGGGGGGG
Log(sr.ReadLine(), true);
}
пока считыватель потока все еще читает, вы можете записывать его, когда выходит журнал.
Ответ 6
Ну, я попробовал класс-помощник, который, как я знаю, работает: http://csharptest.net/browse/src/Library/Processes/ProcessRunner.cs
ProcessRunner runner = new ProcessRunner("iisreset.exe");
runner.OutputReceived += OutputDataReceived;
runner.Start("/RESTART", "/STATUS");
Однако это все еще не решает проблему с этим конкретным исполняемым файлом. Кажется, что iisreset был написан таким образом, что это невозможно. Даже выполнив из командной строки следующее:
iisreset.exe /RESTART /STATUS > temp.txt
Все еще ничего не записывается в текстовый файл temp.txt до тех пор, пока все службы не будут перезапущены.
Что касается вашего примера кода, я бы рекомендовал прочитать сообщение, которое я написал некоторое время назад: Как правильно использовать System.Diagnostics.Process. В частности, вы не читаете поток std:: err или перенаправляете и закрываете std:: in stream. Это может привести к очень нежелательным результатам в вашей программе. Вы можете посмотреть примерный класс-оболочку, связанный выше, как это сделать с выходными событиями, или если вы хотите напрямую прочитать потоки, вам нужно использовать два ваших собственных потока.
static void Main()
{
ProcessStartInfo psi = new ProcessStartInfo(@"C:\Windows\system32\iisreset.exe", "/RESTART /STATUS");
psi.CreateNoWindow = true;
psi.UseShellExecute = false;
psi.RedirectStandardError = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardInput = true;
ManualResetEvent output_complete = new ManualResetEvent(false);
ManualResetEvent error_complete = new ManualResetEvent(false);
Process p = Process.Start(psi);
new ReadOutput(p.StandardOutput, output_complete);
new ReadOutput(p.StandardError, error_complete);
p.StandardInput.Close();
p.WaitForExit();
output_complete.WaitOne();
error_complete.WaitOne();
}
private class ReadOutput
{
private StreamReader _reader;
private ManualResetEvent _complete;
public ReadOutput(StreamReader reader, ManualResetEvent complete)
{
_reader = reader;
_complete = complete;
Thread t = new Thread(new ThreadStart(ReadAll));
t.Start();
}
void ReadAll()
{
int ch;
while(-1 != (ch = _reader.Read()))
{
Console.Write((char) ch);
}
_complete.Set();
}
}
Я написал это только для того, чтобы увидеть, что происходит. Все еще ничего не получалось до конца, поэтому я считаю, что ваш единственный SOL получает асинхронный вывод из iisreset.