C# 从 Process 获取实时输出

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8808663/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 04:31:53  来源:igfitidea点击:

Get Live output from Process

c#redirectprocesslive

提问by Extaze

I've a problem in my project. I would like to launch a process, 7z.exe (console version). I've tried three different things:

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

  • Process.StandardOutput.ReadToEnd();
  • OutputDataReceived & BeginOutputReadLine
  • StreamWriter
  • Process.StandardOutput.ReadToEnd();
  • OutputDataReceived 和 BeginOutputReadLine
  • 流写入器

Nothing works. It always "wait" for the end of the process to show what i want. I don't have any code to put, just if you want my code with one of the things listed upthere. Thanks.

什么都行不通。它总是“等待”过程结束以显示我想要的内容。我没有任何代码可以放置,只要您希望我的代码包含上面列出的其中一项内容。谢谢。

Edit: My code:

编辑:我的代码:

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

Or

或者

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

Or

或者

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

Where "process" is my pre-made Process

其中“流程”是我的预制流程

Ok i know why it doesn't works properly: 7z.exe is the bug: it display a percent loading in console, and it sends information only when the current file is finished. In extraction for example, it works fine :). I will search for another way to use 7z functions without 7z.exe (maybe with 7za.exe or with some DLL). Thanks to all. To answer to the question, OuputDataRecieved event works fine !

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

采纳答案by Michiel van Vaardegem

Take a look at this page, it looks this is the solution for you: http://msdn.microsoft.com/en-us/library/system.diagnostics.process.beginoutputreadline.aspxand http://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput.aspx

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

[Edit] This is a working example:

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

        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:\ lists all files from the root of C: recursively. These are a lot of files, and I'm sure it isn't done when the first results show up in the screen. There is a possibility 7zip holds the output before showing it. I'm not sure what params you give to the proces.

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

回答by sonu thomas

Try this.

尝试这个。

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

Let the above be in a thread.

让上面的在一个thread.

Now for updating the output to UI,you can use a timerwith two lines

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

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

This may help you

这可能会帮助你

回答by joebalt

I have used the CmdProcessor class described hereon several projects with much success. It looks a bit daunting at first but is very easy to use.

我已经在几个项目中使用了这里描述的 CmdProcessor 类,并取得了很大的成功。它起初看起来有点令人生畏,但非常易于使用。

回答by user2848240

I don't know if anyone is still looking for a solution to this, but it has come up several times for me because I'm writing a tool in Unity in support of some games and due to the limited interoperability of certain systems with mono (like PIA for reading text from Word, for example), I often have to write OS-specific (sometimes Windows, sometimes MacOS) executables and launch them from Process.Start().

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

The problem is, when you launch an executable like this it's going to fire up in another thread that blocks your main app, causing a hang. If you want to provide useful feedback to your users during this time beyond the spinning icons conjured up by your respective OS, then you're kind of screwed. Using a stream won't work because the thread is still blocked until execution finishes.

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

The solution I've hit on, which might seem extreme for some people but I find works quite well for me, is to use sockets and multithreading to set up reliable synchronous comms between the two apps. Of course, this only works if you are authoring both apps. If not, I think you are out of luck. ... I would like to see if it works with just multithreading using a traditional stream approach, so if someone would like to try that and post the results here that would be great.

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

Anyway, here's the solution currently working for me:

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

In the main, or calling app, I do something like this:

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

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

Here's where I establish the socket server:

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

/// <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;
}

Here's my socket handler for the thread... note that you will have to create multiple threads in some cases; that's why I have that _builderCommThreads List in there (I ported it from code elsewhere where I was doing something similar but calling multiple instances in a row):

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

/// <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;
}

Of course, you'll need to declare some stuff up at the top:

当然,你需要在顶部声明一些东西:

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

...then in the invoked executable, set up the other end (I used statics in this case, you can use what ever you want):

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

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

...I'm using this to launch a command line tool on Windows that uses the PIA stuff to pull text out of a Word doc. I tried PIA the .dlls in Unity, but ran into interop issues with mono. I'm also using it on MacOS to invoke shell scripts that launch additional Unity instances in batchmode and run editor scripts in those instances that talk back to the tool over this socket connection. It's great, because I can now send feedback to the user, debug, monitor and respond to specific steps in the process, et cetera, et cetera.

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

HTH

HTH

回答by user3042599

To correctly handle output and/or error redirection you must also redirect input. It seem to be feature/bug in runtime of the external application youre starting and from what I have seen so far, it is not mentioned anywhere else.

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

Example usage:

用法示例:

        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
    }

回答by Bork Blatt

The problem is caused by calling the Process.WaitForExitmethod. What this does, according to the documentation, is:

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

Sets the period of time to wait for the associated process to exit, and blocks the current threadof execution until the time has elapsed or the process has exited. To avoid blocking the current thread, use the Exited event.

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

So, to prevent the thread blocking until the process has exited, wire up the Process.Exitedeventhandler of the Process object, as below. The Exited event can occur only if the value of the EnableRaisingEvents property is true.

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

    process.EnableRaisingEvents = true;
    process.Exited += Proc_Exited;


    private void Proc_Exited(object sender, EventArgs e)
    {
        // Code to handle process exit
    }

Done this way, you will be able to get the output of your process, while it is still running, via the Process.OutputDataReceivedeventas you currently do. (PS - the code example on that event page also makes the mistake of using Process.WaitForExit.)

通过这种方式,您将能够在流程仍在运行时通过Process.OutputDataReceived事件获得流程的输出,就像您目前所做的那样。(PS - 该事件页面上的代码示例也犯了使用 Process.WaitForExit 的错误。)

Another note is that you need to ensure your Process object is not cleaned up before your Exited method can fire. If your Process is initialized in a using statement, this might be a problem.

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