【问题标题】:Get Live output from Process从 Process 获取实时输出
【发布时间】:2012-02-07 04:23:09
【问题描述】:

我的项目有问题。我想启动一个进程,7z.exe(控制台版本)。 我尝试了三种不同的方法:

  • Process.StandardOutput.ReadToEnd();
  • OutputDataReceived & BeginOutputReadLine
  • StreamWriter

没有任何作用。它总是“等待”过程结束以显示我想要的东西。 我没有任何代码可以放,只是如果你想要我的代码与上面列出的东西之一。谢谢。

编辑: 我的代码:

        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.CreateNoWindow = true;
        process.Start();

        this.sr = process.StandardOutput;
        while (!sr.EndOfStream)
        {
            String s = sr.ReadLine();
            if (s != "")
            {
                System.Console.WriteLine(DateTime.Now + " - " + s);
            }
        }

或者

process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += new DataReceivedEventHandler(recieve);
process.StartInfo.CreateNoWindow = true;
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
public void recieve(object e, DataReceivedEventArgs outLine)
{
    System.Console.WriteLine(DateTime.Now + " - " + outLine.Data);
}

或者

process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
string output = p.StandardOutput.ReadToEnd();
process.WaitForExit();

“流程”是我的预制流程

好的,我知道为什么它不能正常工作:7z.exe 是错误:它在控制台中显示加载百分比,并且仅在当前文件完成时发送信息。例如,在提取中,它工作得很好:)。我将寻找另一种在没有 7z.exe 的情况下使用 7z 函数的方法(可能使用 7za.exe 或一些 DLL)。谢谢大家。 要回答这个问题,OuputDataRecieved 事件可以正常工作!

【问题讨论】:

  • 您不使用 7zip 中的 DLL/SDK 下载的任何原因,它允许比任何基于控制台的技术更好的控制?
  • 查看您在 Process 中尝试过的代码会有所帮助,例如在哪里创建 Process
  • 因为 7z.exe 涵盖了我想要的所有功能。
  • 起来?我真的需要帮助。我会尝试使用cmd。我认为问题来自 7z.exe。

标签: c# redirect process live


【解决方案1】:

试试这个。

        Process notePad = new Process();

        notePad.StartInfo.FileName = "7z.exe";
        notePad.StartInfo.RedirectStandardOutput = true;
        notePad.StartInfo.UseShellExecute = false;

        notePad.Start();
        StreamReader s = notePad.StandardOutput;



        String output= s.ReadToEnd();


        notePad.WaitForExit();

将上面的内容放在thread中。

现在要将输出更新到 UI,您可以使用带有两行的 timer

  Console.Clear();
  Console.WriteLine(output);

这可能对你有帮助

【讨论】:

  • 据我所知,这段代码也会等到它完成。如问题所述,他不想等到完成,而是想要实时输出
  • 正如 Michiel 所说,这是为了获得“最终”输出
【解决方案2】:

看看这个页面,看起来这是适合你的解决方案:http://msdn.microsoft.com/en-us/library/system.diagnostics.process.beginoutputreadline.aspxhttp://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput.aspx

[编辑] 这是一个工作示例:

        Process p = new Process();
        p.StartInfo.RedirectStandardError = true;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.FileName = @"C:\Program Files (x86)\gnuwin32\bin\ls.exe";
        p.StartInfo.Arguments = "-R C:\\";

        p.OutputDataReceived += new DataReceivedEventHandler((s, e) => 
        { 
            Console.WriteLine(e.Data); 
        });
        p.ErrorDataReceived += new DataReceivedEventHandler((s, e) =>
        {
            Console.WriteLine(e.Data);
        });

        p.Start();
        p.BeginOutputReadLine();
        p.BeginErrorReadLine();

顺便说一句,ls -R C:\ 从 C: 的根目录递归列出所有文件。这些是很多文件,当第一个结果出现在屏幕上时,我确信它还没有完成。 7zip 有可能在显示之前保存输出。我不确定你给进程提供了什么参数。

【讨论】:

  • 我能找到的,是你应该使用事件OutputDataReceived,目前我没有时间测试它,也许以后。 (harbertc.wordpress.com/2006/05/16/…)
  • 我什么都试过了,但问题是 7z.exe。我决定为 c# (SevenZipSharp) 使用 7z 库,问题就解决了。谢谢;)
  • 我在 7-zip 和输出流重定向方面遇到了完全相同的问题。如果只看 7-zip 的输出,很明显它没有执行标准控制台输出;相反,它似乎正在调用控制台函数来重写显示屏上的内容以显示进度百分比。直到最后才得到输出的原因是它直到最后才写入标准控制台输出,作为一种最终日志。
  • BeginOutputReadLine() 在测试存档时完美适用于 7z.exe
  • p.BeginErrorReadLine(); 是必需的,否则错误处理程序不会被调用
【解决方案3】:

我在几个项目中使用了here 描述的 CmdProcessor 类并取得了很大的成功。乍一看有点吓人,但使用起来非常简单。

【讨论】:

    【解决方案4】:

    我不知道是否有人仍在寻找解决此问题的方法,但我已经多次遇到这种情况,因为我正在 Unity 中编写一个工具来支持某些游戏,并且由于某些游戏的互操作性有限具有单声道的系统(例如,用于从 Word 读取文本的 PIA),我经常必须编写特定于操作系统(有时是 Windows,有时是 MacOS)的可执行文件并从 Process.Start() 启动它们。

    问题是,当您启动这样的可执行文件时,它会在另一个线程中启动,从而阻塞您的主应用程序,从而导致挂起。如果您想在这段时间内向您的用户提供有用的反馈,而不是您各自的操作系统产生的旋转图标,那么您有点搞砸了。使用流是行不通的,因为线程在执行完成之前仍然被阻塞。

    我想到的解决方案(对某些人来说可能看起来很极端,但我发现对我来说效果很好)是使用套接字和多线程在两个应用程序之间建立可靠的同步通信。当然,这仅在您同时编写两个应用程序时才有效。如果没有,我认为你不走运。 ...我想看看它是否适用于使用传统流方法的多线程,所以如果有人想尝试并在此处发布结果,那就太好了。

    无论如何,这是目前对我有用的解决方案:

    在主应用程序或调用应用程序中,我执行以下操作:

    /// <summary>
    /// Handles the OK button click.
    /// </summary>
    private void HandleOKButtonClick() {
    string executableFolder = "";
    
    #if UNITY_EDITOR
    executableFolder = Path.Combine(Application.dataPath, "../../../../build/Include/Executables");
    #else
    executableFolder = Path.Combine(Application.dataPath, "Include/Executables");
    #endif
    
    EstablishSocketServer();
    
    var proc = new Process {
        StartInfo = new ProcessStartInfo {
            FileName = Path.Combine(executableFolder, "WordConverter.exe"),
            Arguments = locationField.value + " " + _ipAddress.ToString() + " " + SOCKET_PORT.ToString(), 
            UseShellExecute = false,
            RedirectStandardOutput = true,
            CreateNoWindow = true
        }
    };
    
    proc.Start();
    

    这里是我建立套接字服务器的地方:

    /// <summary>
    /// Establishes a socket server for communication with each chapter build script so we can get progress updates.
    /// </summary>
    private void EstablishSocketServer() {
        //_dialog.SetMessage("Establishing socket connection for updates. \n");
        TearDownSocketServer();
    
        Thread currentThread;
    
        _ipAddress = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0];
        _listener = new TcpListener(_ipAddress, SOCKET_PORT);
        _listener.Start();
    
        UnityEngine.Debug.Log("Server mounted, listening to port " + SOCKET_PORT);
    
        _builderCommThreads = new List<Thread>();
    
        for (int i = 0; i < 1; i++) {
            currentThread = new Thread(new ThreadStart(HandleIncomingSocketMessage));
            _builderCommThreads.Add(currentThread);
            currentThread.Start();
        }
    }
    
    /// <summary>
    /// Tears down socket server.
    /// </summary>
    private void TearDownSocketServer() {
        _builderCommThreads = null;
    
        _ipAddress = null;
        _listener = null;
    }
    

    这是我的线程套接字处理程序...请注意,在某些情况下您必须创建多个线程;这就是为什么我在那里有那个 _builderCommThreads 列表的原因(我从其他地方的代码中移植了它,我正在做类似的事情但连续调用多个实例):

    /// <summary>
    /// Handles the incoming socket message.
    /// </summary>
    private void HandleIncomingSocketMessage() {
        if (_listener == null) return;
    
        while (true) {
            Socket soc = _listener.AcceptSocket();
            //soc.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 10000);
            NetworkStream s = null;
            StreamReader sr = null;
            StreamWriter sw = null;
            bool reading = true;
    
            if (soc == null) break;
    
            UnityEngine.Debug.Log("Connected: " + soc.RemoteEndPoint);
    
            try {
                s = new NetworkStream(soc);
                sr = new StreamReader(s, Encoding.Unicode);
                sw = new StreamWriter(s, Encoding.Unicode);
                sw.AutoFlush = true; // enable automatic flushing
    
                while (reading == true) {
                    string line = sr.ReadLine();
    
                    if (line != null) {
                        //UnityEngine.Debug.Log("SOCKET MESSAGE: " + line);
                        UnityEngine.Debug.Log(line);
    
                        lock (_threadLock) {
                            // Do stuff with your messages here
                        }
                    }
                }
    
                //
            } catch (Exception e) {
                if (s != null) s.Close();
                if (soc != null) soc.Close();
                UnityEngine.Debug.Log(e.Message);
                //return;
            } finally {
    
            //
            if (s != null) s.Close();
            if (soc != null) soc.Close();
    
            UnityEngine.Debug.Log("Disconnected: " + soc.RemoteEndPoint);
            }
        }
    
        return;
    }
    

    当然,您需要在顶部声明一些内容:

    private TcpListener _listener = null;
    private IPAddress _ipAddress = null;
    private List<Thread> _builderCommThreads = null;
    private System.Object _threadLock = new System.Object();
    

    ...然后在调用的可执行文件中,设置另一端(在这种情况下我使用静态,你可以使用任何你想要的):

    private static TcpClient _client = null;
    private static Stream _s = null;
    private static StreamReader _sr = null;
    private static StreamWriter _sw = null;
    private static string _ipAddress = "";
    private static int _port = 0;
    private static System.Object _threadLock = new System.Object();
    
    /// <summary>
    /// Main method.
    /// </summary>
    /// <param name="args"></param>
    static void Main(string[] args) {
        try {
            if (args.Length == 3) {
                _ipAddress = args[1];
                _port = Convert.ToInt32(args[2]);
    
                EstablishSocketClient();
            }
    
            // Do stuff here
    
            if (args.Length == 3) Cleanup();
        } catch (Exception exception) {
            // Handle stuff here
            if (args.Length == 3) Cleanup();
        }
    }
    
    /// <summary>
    /// Establishes the socket client.
    /// </summary>
    private static void EstablishSocketClient() {
        _client = new TcpClient(_ipAddress, _port);
    
        try {
            _s = _client.GetStream();
            _sr = new StreamReader(_s, Encoding.Unicode);
            _sw = new StreamWriter(_s, Encoding.Unicode);
            _sw.AutoFlush = true;
        } catch (Exception e) {
            Cleanup();
        }
    }
    
    /// <summary>
    /// Clean up this instance.
    /// </summary>
    private static void Cleanup() {
        _s.Close();
        _client.Close();
    
        _client = null;
        _s = null;
        _sr = null;
        _sw = null;
    }
    
    /// <summary>
    /// Logs a message for output.
    /// </summary>
    /// <param name="message"></param>
    private static void Log(string message) {
        if (_sw != null) {
            _sw.WriteLine(message);
        } else {
            Console.Out.WriteLine(message);
        }
    }
    

    ...我正在使用它在 Windows 上启动一个命令行工具,该工具使用 PIA 内容从 Word 文档中提取文本。我在 Unity 中尝试了 PIA .dll,但遇到了单声道的互操作问题。我还在 MacOS 上使用它来调用 shell 脚本,这些脚本以批处理模式启动其他 Unity 实例,并在那些通过此套接字连接与工具对话的实例中运行编辑器脚本。这很棒,因为我现在可以向用户发送反馈、调试、监控和响应流程中的特定步骤等等。

    HTH

    【讨论】:

    • 虽然从技术上讲不是问题的答案,但这是密切相关且非常有帮助的。
    【解决方案5】:

    要正确处理输出和/或错误重定向,您还必须重定向输入。 它似乎是您正在启动的外部应用程序运行时的功能/错误,从我目前看到的情况来看,其他任何地方都没有提到它。

    示例用法:

            Process p = new Process(...);
    
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.RedirectStandardInput = true; // Is a MUST!
            p.EnableRaisingEvents = true;
    
            p.OutputDataReceived += OutputDataReceived;
            p.ErrorDataReceived += ErrorDataReceived;
    
            Process.Start();
    
            p.BeginOutputReadLine();
            p.BeginErrorReadLine();
    
            p.WaitForExit();
    
            p.OutputDataReceived -= OutputDataReceived;
            p.ErrorDataReceived -= ErrorDataReceived;
    

    ...

        void OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            // Process line provided in e.Data
        }
    
        void ErrorDataReceived(object sender, DataReceivedEventArgs e)
        {
            // Process line provided in e.Data
        }
    

    【讨论】:

    • 标准输入的重定向为我解决了这个问题。
    【解决方案6】:

    问题是由调用Process.WaitForExit method 引起的。根据文档,它的作用是:

    设置等待关联进程退出的时间段, 并阻塞当前线程的执行,直到时间过去 或进程已退出。 为避免阻塞当前线程,请使用 退出事件

    因此,为了防止线程阻塞直到进程退出,请连接 Process 对象的 Process.Exited event 处理程序,如下所示。仅当 EnableRaisingEvents 属性的值为 true 时,才会发生 Exited 事件。

        process.EnableRaisingEvents = true;
        process.Exited += Proc_Exited;
    
    
        private void Proc_Exited(object sender, EventArgs e)
        {
            // Code to handle process exit
        }
    

    通过这种方式,您将能够像当前一样通过Process.OutputDataReceived event 获取仍在运行的进程的输出。 (PS - 该事件页面上的代码示例也犯了使用 Process.WaitForExit 的错误。)

    另一个注意事项是,在您的 Exited 方法可以触发之前,您需要确保您的 Process 对象没有被清理。如果你的 Process 是在 using 语句中初始化的,这可能是个问题。

    【讨论】:

      【解决方案7】:

      Windows 以不同的方式处理管道和控制台。管道被缓冲,控制台没有。 RedirectStandardOutput 连接管道。只有两种解决方案。

      1. 更改控制台应用程序以在每次写入后刷新其缓冲区
      2. 根据https://www.codeproject.com/Articles/16163/Real-Time-Console-Output-Redirection 编写一个 shim 来伪造控制台

      请注意,RTConsole 不处理遇到相同问题的 STDERR。

      感谢 https://stackoverflow.com/users/4139809/jeremy-lakeman 与我分享有关另一个问题的信息。

      【讨论】:

        猜你喜欢
        • 2021-12-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-30
        • 2021-04-15
        相关资源
        最近更新 更多