获取 .NET Process 对象以连续刷新输入流?

发布于 2024-11-18 20:07:06 字数 2092 浏览 2 评论 0原文

我正在尝试更改与 Mercurial 命令行客户端对话的类库,1.9 客户端中的新功能是能够启动服务器并通过标准输入/输出管道与其对话。这确实很有希望,因为使用 Mercurial 命令行客户端的主要难题之一是,由于它是用 Python 编写的,因此即使只是询问其版本,也会有一些启动客户端的开销。

然而,有一个问题。到目前为止,我已经通过读取标准输出/错误从命令行客户端检索输出,并且当进程退出时,流刷新,我可以读取流的末尾。

然而,在这种新模式下,进程会将大量文本转储到标准错误/输出,然后等待下一个命令。非常适合减少开销,但很糟糕,因为 .NET 会缓冲这些流。

换句话说,由于进程没有退出,所以我不会得到该输出的最后部分,直到:

  1. 我要求进程退出
  2. 我发出另一个命令

有没有办法让我

  1. 要求缓冲区刷新,这意味着我得到缓冲区中的内容是什么,即使它不足以使缓冲区自行刷新?
  2. 如果不是,我可以将缓冲区大小设置为 1 个字符吗?
  3. 我还能做些什么吗?

如果需要的话,我可以处理 P/Invoke。

这是一个 LINQPad 概念验证程序,它可以说明:

它的作用是启动命令提示符并向其提供 DIR命令,但请注意,直到循环内经过 10 秒后,程序才会输出“C:>”生成目录列表后立即输出命令提示符的提示。

换句话说,这就是命令提示符的作用:

  1. 生成目录列表
  2. 通过提示“C:>\”请求另一个命令

但是,这是下面的程序所看到的:

  1. 生成目录列表
  2. (等待 10 秒)
  3. 关闭输入流
  4. 查看提示

    无效主函数() { 字符串 clientExecutablePath = "cmd.exe";

    var psi = new ProcessStartInfo();
    psi.FileName = clientExecutablePath;
    psi.RedirectStandardError = true;
    psi.RedirectStandardInput = true;
    psi.RedirectStandardOutput = true;
    psi.CreateNoWindow = true;
    psi.WorkingDirectory = @"C:\";
    psi.WindowStyle = ProcessWindowStyle.Hidden;
    psi.UseShellExecute = false;
    psi.ErrorDialog = false;
    psi.StandardErrorEncoding = Encoding.GetEncoding("Windows-1252");
    psi.StandardOutputEncoding = Encoding.GetEncoding("Windows-1252");
    
    var p = Process.Start(psi);
    
    var 输入 = p.StandardInput;
    var 输出 = p.StandardOutput;
    var 错误 = p.StandardError;
    
    动作 reader = delegate(StreamReader StreamReader, 字符串前缀)
    {
        串线;
        while ((line = StreamReader.ReadLine()) != null)
        {
            Debug.WriteLine(前缀+行);
        }
    };
    
    IAsyncResult outputReader = reader.BeginInvoke(output, "o: ", null, null);
    IAsyncResult errorReader = reader.BeginInvoke(error, "e: ", null, null);
    
    输入.Write("目录\n");
    输入.Flush();
    
    while (!p.HasExited)
    {
        线程.睡眠(10000);
        输入.关闭();
    }
    
    reader.EndInvoke(outputReader);
    reader.EndInvoke(errorReader);
    

    }

I am trying to change my class library that talks to the Mercurial command line client, and new in the 1.9 client is the ability to spin up a server and talk to it over the standard input/output pipes. This is really promising since one of the major headaches of using the Mercurial command line client is that since it is written in Python, there's a bit of overhead spinning up the client, even for just asking it for its version.

However, there is one problem. Up until now I have retrieved the output from the command line client by reading the standard output/error, and when the process exits, the streams flush and I can read the end of the stream.

However, in this new mode, the process will dump a lot of text to the standard error/output, and then sit around waiting for the next command. Great for reducing overhead, but lousy since .NET buffers those streams.

In other words, since the process does not exit, I do not get the last portion of that output until:

  1. I ask the process to exit
  2. I issue another command

Is there a way for me to

  1. Ask the buffer to flush, meaning that I get whatever is in the buffer, even if it is not enough to make the buffer flush by itself?
  2. If not, can I set the buffer-size to 1 character?
  3. Anything else I can do?

I can deal with P/Invoke if that is what it takes.

Here's a LINQPad proof-of-concept program that would illustrate:

What it does is spin up a command prompt and feed it the DIR command, however notice that not until those 10 seconds have elapsed inside the loop does the program output the "C:>" prompt that the command prompt outputted right after producing the directory listing.

In other words, this is what the command prompt does:

  1. Produce directory listing
  2. Ask for another command by prompting "C:>\"

However, this is what the program below sees:

  1. Produce directory listing
  2. (wait 10 seconds)
  3. Close the input stream
  4. See the prompt

    void Main()
    {
    string clientExecutablePath = "cmd.exe";

    var psi = new ProcessStartInfo();
    psi.FileName = clientExecutablePath;
    psi.RedirectStandardError = true;
    psi.RedirectStandardInput = true;
    psi.RedirectStandardOutput = true;
    psi.CreateNoWindow = true;
    psi.WorkingDirectory = @"C:\";
    psi.WindowStyle = ProcessWindowStyle.Hidden;
    psi.UseShellExecute = false;
    psi.ErrorDialog = false;
    psi.StandardErrorEncoding = Encoding.GetEncoding("Windows-1252");
    psi.StandardOutputEncoding = Encoding.GetEncoding("Windows-1252");
    
    var p = Process.Start(psi);
    
    var input = p.StandardInput;
    var output = p.StandardOutput;
    var error = p.StandardError;
    
    Action<StreamReader, string> reader = delegate(StreamReader streamReader, string prefix)
    {
        string line;
        while ((line = streamReader.ReadLine()) != null)
        {
            Debug.WriteLine(prefix + line);
        }
    };
    
    IAsyncResult outputReader = reader.BeginInvoke(output, "o: ", null, null);
    IAsyncResult errorReader = reader.BeginInvoke(error, "e: ", null, null);
    
    input.Write("dir\n");
    input.Flush();
    
    while (!p.HasExited)
    {
        Thread.Sleep(10000);
        input.Close();
    }
    
    reader.EndInvoke(outputReader);
    reader.EndInvoke(errorReader);
    

    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

泪冰清 2024-11-25 20:07:06

我不认为有任何方法可以强制刷新,但是我有几个可能对您有帮助的替代方案:

  1. 我注意到您正在使用 ReadLine() 来读取流。直到写入完整的输出行(包括 CRLF,或至少 LF)后才会返回。因此,您可能需要考虑使用 Read(),它只会读取单个字符。您可能会发现,如果服务器进程在退出之前没有在最后一行写入 CRLF,那么这会为您提供您要查找的最后一个输出块。
  2. 您可以通过向 Process 上的 OutputDataReceived 和 ErrorDataReceived 事件添加处理程序来异步读取流,然后使用 BeginOutputReadLine 和 BeginErrorReadLine 开始接收数据。我不知道事件被调用的频率是多少:如果有任何数据可用,则定期调用,每当收到完整的数据行时,或者针对每个字符。

HTH,

巴特

I don't think there's any way to force a flush, however I have a couple of alternatives which might help you:

  1. I notice you're using ReadLine() to read the stream. This won't return until a complete line of output (including CRLF, or at least LF) has been written. You might therefore want to consider using Read() instead, which will read only a single character. You may find this gets you the last chunk of output you're looking for if the server process isn't writing a CRLF on the last line until it exits.
  2. You could read the streams asynchronously by adding handlers to the OutputDataReceived and ErrorDataReceived events on the Process, then use BeginOutputReadLine and BeginErrorReadLine to start receiving data. I don't know how often the events are invoked though: at regular intervals if any data is available, whenever a complete line of data is received, or for every character.

HTH,

Bart

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文