Подтвердить что ты не робот

Захват вывода процесса синхронно (т.е. "Когда это произойдет" )

Я пытаюсь запустить процесс и захватить вывод, пришел далеко, но я не совсем в том решении, которое я бы хотел.

В частности, я пытаюсь использовать 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 ", когда это произойдет", чтобы я мог показать его как индикацию прогресса.

Я видел еще один поток с той же/подобной проблемой, Асинхронный захват с выхода процесса не работает должным образом, но без решения.

Я как бы тупой.

4b9b3361

Ответ 1

В моей конкретной ситуации решение - это то, что предложил г-н Моисей в комментарии выше, то есть запустите iisreset /stop, а затем iisreset /start.

Мне нужен правильный ответ, а не комментарий, чтобы отметить его как "принятый ответ", поэтому этот ответ скорее административен, чем новый. Кредит должен пойти к господину Моисею..: -)

Ответ 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.