【问题标题】:Unable to read output from child process from C# app using async methods?无法使用异步方法从 C# 应用程序读取子进程的输出?
【发布时间】:2015-12-24 09:14:58
【问题描述】:

我正在尝试使用 C# 和 Visual Studio 2013 Update 4 在我的 Windows 8.1 机器上为 EXE 编写我自己的交互式 shell。代码运行时没有任何错误,但我在任何一个中的读取调用中都看不到任何返回从 stdout 读取的循环或读取 stderr 的循环。读取调用永远不会返回。我的方法有什么问题?

我看到了这个相关的 SO 帖子,但我根本没有收到任何异常:

C# Read stdout of child process asynchronously

public partial class frmMain : Form
{
    private const string DIR_CHILDPROCESS = @"C:\Users\Robert\Documents\Visual Studio 2012\ChildProcess-5.3.1 - IDAvatars\";
    private const string EXE_NAME = @"childprocess.exe";

    Process _childProcess = null;

    public frmMain()
    {
        InitializeComponent();
    }

    private async Task ReadOutputFromChildProcess()
    {
        char[] aryC = new char[1];

        while (true)
        {
            // Get output from ChildProcess.
            int count = await this._childProcess.StandardOutput.ReadAsync(aryC, 0, 1);

            Debug.Write(aryC[0]);

            // await Task.Delay(50);
        }
    }

    private async Task ReadFromErrorsChildProcess()
    {
        char[] aryC = new char[1];

        while (true)
        {
            // Get output from ChildProcess.
            int count = await this._childProcess.StandardError.ReadAsync(aryC, 0, 1);

            Debug.Write(aryC[0]);

            // await Task.Delay(50);
        }
    }

    private void frmShell_Load(object sender, EventArgs e)
    {
        string target = Path.Combine(DIR_CHILDPROCESS, EXE_NAME);

        this._childProcess = new Process();

        this._childProcess.StartInfo.UseShellExecute = false;
        this._childProcess.StartInfo.RedirectStandardOutput = true;
        this._childProcess.StartInfo.RedirectStandardError = true;
        this._childProcess.StartInfo.RedirectStandardInput = true;
        this._childProcess.StartInfo.CreateNoWindow = true; 

        this._childProcess.EnableRaisingEvents = true;

        this._childProcess.StartInfo.FileName = target;

        this._childProcess.Exited += this._childProcess_Exited;

        // this._childProcess.StartInfo.Arguments = arguments;
        // this._childProcess.StartInfo.
        this._childProcess.StartInfo.WorkingDirectory = DIR_CHILDPROCESS;
        //this._childProcess.StartInfo.FileName = Path.Combine(DIR_CHILDPROCESS, EXE_NAME);
        //this._childProcess.StartInfo.UseShellExecute = false;
        //this._childProcess.StartInfo.RedirectStandardOutput = true;
        //this._childProcess.StartInfo.RedirectStandardError = true;
        //this._childProcess.StartInfo.RedirectStandardInput = true;
        //this._childProcess.StartInfo.CreateNoWindow = true;


        if (!this._childProcess.Start())
            MessageBox.Show("ChildProcess failed to start.", "Error");

        // Start the loop that reads output from ChildProcess.
        this.ReadOutputFromChildProcess();

        // Start the loop that reads error messages from ChildProcess.
        this.ReadFromErrorsChildProcess();

        Debug.WriteLine("ChildProcess launched successfully.");

        // this._childProcess.WaitForExit();

    }

    void _childProcess_Exited(object sender, EventArgs e)
    {
        Debug.WriteLine("Child process exited with exit code: " + this._childProcess.ExitCode.ToString());
    }

    private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
    {
        this._childProcess.Kill();
    }
}

【问题讨论】:

  • 子进程是否退出?如果没有,您可能需要关闭其输入句柄。它实际上是在产生输出吗?如果是,它实际上是在写入它的输出流还是直接将它发送到它的控制台?即,尝试在 cmd 中使用重定向输出执行应用程序;这行得通吗?
  • @StephenCleary - 只要主机 shell 应用程序处于活动状态,我想保持子进程处于活动状态,因此它不会退出(我通过确保子进程显示在任务管理器)。它肯定会产生输出。我尝试从 CMD 启动重定向输出,但捕获文件中没有出现任何内容。所以我认为这意味着它直接写入控制台而不是 StdOut?如果是这样,在这种情况下如何捕获输出?我认为这一定是可能的,因为 CMD 和其他 shell 这样做。
  • 从技术上讲,保持子应用程序“活着”是不可能的;它会随时退出;您能做的最好的事情就是保留对它的引用(作为僵尸进程)。听起来您的子应用程序正在直接写入控制台,which is bad for you。可用的最佳解决方案(都不好):p/Invoke some nasty APIshook the child process' calls
  • @StephenCleary 谢谢。保持子应用程序活着没有问题。在您输入“退出”之前它不会退出。第二点,子应用程序是使用 printf() 的 C++ 应用程序。尚未确认,但看起来 printf() 确实在 Linux 上打印到标准输出,但不是 Win32。也许是 C++ CRT lib 的东西?在上面找到这篇文章:gamedev.net/topic/…
  • @StephenCleary - 使用 Visual Studio 构建的使用 printf 的应用似乎不输出到标准输出:discuss.cocos2d-x.org/t/printf-stdout-do-not-work-on-win32/315/…

标签: c# asynchronous process stdout stdin


【解决方案1】:

我看到了这个相关的 SO 帖子,但没有抛出任何异常 完全没有

从任何异步方法传播的任何异常都将被两个 Task 对象吞没,您无需等待任何对象。如果您想观察任何异常,您可以同时执行两个异步操作并使用Task.WhenAll 等待它们:

private async void frmShell_Load(object sender, EventArgs e)
{
    var readOutputTask = this.ReadOutputFromChildProcess();
    var readErrorTask = this.ReadFromErrorsChildProcess();

    await Task.WhenAll(readOutputTask, readErrorTask);
}

否则,如果你想让这两个操作顺序执行,你可以await每一个单独执行:

private async void frmShell_Load(object sender, EventArgs e)
{
    await ReadOutputFromChildProcess();
    await ReadFromErrorsChildProcess();
}

【讨论】:

  • 谢谢。我按照你的建议做了,并使用了“await Task.WhenAll()”。但是,我仍然没有从我的 Read() 调用中得到任何异常或任何结果。奇怪的是,当我在退出应用程序的途中关闭主窗体时,读取调用最终确实返回了一些东西。此时,表单 Load 事件位于堆栈顶部。我开始怀疑是否发生了某种阻塞。
  • 有趣。关闭应用程序时发生某些事情的原因是因为我的代码中有(有)Debug.Write() 语句的错误。我使用 ary[1] 而不是 ary[0],导致索引超出范围异常。一旦我纠正了这个错误,关闭应用程序时不再命中 Debug.Write() 语句上的断点。请注意,我刚刚更正了帖子中代码中的这些陈述。
  • @Robert 那么目前状态如何?
  • 仍然没有运行。尽管按照您的建议更改了我的代码,但我没有收到任何异常,也没有从 Read 调用中获得任何结果。
  • @RobertOschler 进程实际上是否将任何内容输出到输出流?你能用进程监视器看到什么吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多