同步捕获进程的输出(即“当它发生时”)

时间:2011-07-06 14:09:07

标签: c# .net

我正在尝试启动一个过程并捕获输出,已经走了很远的路,但我还没有达到我想要的解决方案。

具体来说,我正在尝试从我正在编写的小型实用程序应用程序重置我的开发计算机上的IIS。我通过实验得出结论,安全的方法是在子进程中运行iisreset.exe

如果在命令提示符下运行iisreset.exe,则会在此过程中收到反馈。运行iisreset需要几秒钟,并且会生成几行反馈,其间会有暂停。

我想捕获这些反馈并将其呈现在我的Windows窗体应用程序中(在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窗体应用程序):

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&#34获得输出,我会很乐意;当它发生时#34;所以我可以把它显示为进度指示。

我已经看到另一个具有相同/类似问题的帖子Asynchronous capture from a process output not working properly,但没有解决方案。

我有点难过。

6 个答案:

答案 0 :(得分:2)

自动执行printfs / stdouts

C equivalent of autoflush (flush stdout after each write)?

这救了我的屁股......

答案 1 :(得分:1)

似乎 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();
    }
}

请注意,当我获得每一行时,我正在加时间戳。对于我的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如何生成阻止此操作的输出。

感谢参与和贡献的人们!

答案 2 :(得分:0)

嗯......你可以踢它老派。可以使用旧式DOS命令(foo.exe | bar.exe)将输出重定向到另一个程序的输入。编写一个从标准读取的程序,每次流刷新时都会得到它。

修改

您还可以将输出重定向到命名管道并从中读取。这也将“正好发生”。

答案 3 :(得分:0)

我遇到了这个问题,当我的日志在一个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);
        }

当流阅读器仍在阅读时,您可以在日志出来时将其写下来。

答案 4 :(得分:0)

好吧,我尝试过一个我知道有用的帮助类: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'。

至于你的示例代码,我建议你阅读我前一段时间写的帖子:How to use System.Diagnostics.Process correctly。具体来说,您不是在读取std :: err流或重定向和关闭std :: in流。这可能会导致程序中出现非常不理想的结果。您可以查看上面链接的示例包装类,了解如何使用输出事件,或者如果您想直接读取需要使用两个自己的线程的流。

    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();
        }
    }

我写这篇文章只是为了看看是否有任何事情发生。直到最后仍然没有任何结果,所以我认为你从iisreset获得异步输出只是SOL。

答案 5 :(得分:0)

对于我的具体情况,解决方案是摩西先生在上述评论中建议的,即运行iisreset /stop后跟iisreset /start

我需要一个正确的答案,而不是评论,以便将其标记为我的“已接受的答案”,所以这个答案更多的是管理而不是新的贡献。应该归功于摩西先生......: - )