从Process获取实时输出

时间:2012-01-10 18:42:07

标签: c# redirect process live

我的项目有问题。我想启动一个进程,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事件工作正常!

7 个答案:

答案 0 :(得分:20)

看一下这个页面,看起来这是适合您的解决方案: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();

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

答案 1 :(得分:5)

我不知道是否还有人在寻找解决方案,但我已多次提出这个问题,因为我在Unity编写了一个支持某些游戏的工具,并且由于某些系统与单声道的有限互操作性(例如PIA用于从Word读取文本),我经常需要编写特定于操作系统(有时是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

答案 2 :(得分:5)

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

使用示例:

Public Class Form1

    Private Const WM_APP As Integer = &H8000
    Private Const WM_FAXMSG As Integer = WM_APP + 9999 ' set by you, send this  in the `AppMsgNumber` parameter when you register for feedback

    Protected Overrides Sub WndProc(ByRef m As Message)
        Select Case m.Msg
            Case WM_FAXMSG
                Dim Status As Integer = m.WParam.ToInt32
                Dim FaxID As Integer = m.LParam.ToInt32
                Debug.Print("Status: " & Status)
                Debug.Print("FaxID: " & FaxID)
        End Select

        MyBase.WndProc(m)
    End Sub

End Class

...

        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;

答案 3 :(得分:1)

我在几个项目中使用了here描述的CmdProcessor类,取得了很大的成功。它起初看起来有点令人生畏,但很容易使用。

答案 4 :(得分:0)

试试这个。

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

这可能对您有所帮助

答案 5 :(得分:0)

问题是由调用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语句中初始化的,则可能是一个问题。

答案 6 :(得分:0)

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 与我分享有关另一个问题的信息。