【问题标题】:C#: Read on subprocess stdout blocks until ANOTHER subprocess finishes?C#:在另一个子进程完成之前读取子进程标准输出块?
【发布时间】:2023-04-10 14:26:04
【问题描述】:

这是我用来启动子进程并监控其输出的 C# 代码:

using (process = new Process()) {
    process.StartInfo.FileName = executable;
    process.StartInfo.Arguments = args;
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardInput = true;
    process.StartInfo.CreateNoWindow = true;
    process.Start();

    using (StreamReader sr = process.StandardOutput) {
        string line = null;
        while ((line = sr.ReadLine()) != null) {
            processOutput(line);
        }
    }

    if (process.ExitCode == 0) {
        jobStatus.State = ActionState.CompletedNormally;
        jobStatus.Progress = 100;
    } else {
        jobStatus.State = ActionState.CompletedAbnormally;
    }
    OnStatusUpdated(jobStatus);
}

我在单独的 ThreadPool 线程中启动多个子进程(但在四核机器上一次不超过四个)。这一切都很好。

我遇到的问题是我的一个子进程将退出,但对sr.ReadLine() 的相应调用将阻塞,直到我的另一个子进程退出。我不确定它会返回什么,但除非我缺少某些东西,否则这不应该发生。

我的子进程没有任何东西会导致它们以任何方式“链接”——它们不会相互通信。发生这种情况时,我什至可以查看任务管理器/进程资源管理器,看到我的子进程实际上已退出,但在其标准输出上对 ReadLine() 的调用仍处于阻塞状态!

我已经能够通过将输出监视代码转出到一个新线程并执行process.WaitForExit() 来解决它,但这似乎是非常奇怪的行为。有人知道这里发生了什么吗?

【问题讨论】:

    标签: c# pipe subprocess


    【解决方案1】:

    关于 ProcessStartInfo.RedirectStandardOutput 的 MSDN 文档详细讨论了在执行您正在执行的操作时可能出现的死锁。提供了一个使用 ReadToEnd 的解决方案,但我想当您使用 ReadLine 时,同样的建议和补救措施也适用。

    同步读操作介绍 调用者之间的依赖关系 从 StandardOutput 流中读取 和子进程写入 溪流。这些依赖可能导致 死锁条件。当来电者 从 a 的重定向流中读取 子进程,它依赖于 孩子。调用者等待读取 操作直到孩子写到 流或关闭流。什么时候 子进程写入足够的数据 为了填充它的重定向流,它是 依赖于父母。孩子 进程等待下一次写入 操作直到父级读取 完整的流或关闭流。 当出现死锁条件时 调用者和子进程等待 互相完成一个操作, 两者都不能继续。你可以 通过评估避免死锁 调用者和调用者之间的依赖关系 子进程。

    最好的解决方案似乎是异步 I/O 而不是同步方法:

    您可以使用异步读取 避免这些依赖的操作 及其陷入僵局的可能性。 或者,您可以避免 通过创建两个死锁条件 线程并读取每个线程的输出 在单独的线程上流式传输。

    如果你走这条路,有一个示例here 应该对你有用。

    【讨论】:

    • 如果我错了,请纠正我,但我不认为这就是这里发生的事情,因为子进程 实际上已经退出并且不再运行。另外,我现在正在创建一个额外的线程并读取该线程上的输出,正如您最后的报价所暗示的那样。
    • @jnylen - 你可能是对的,但你描述的行为对我来说确实听起来像是一个僵局,通过进一步的进程退出得到缓解。如果其他进程没有退出,那么您的第一个进程会永远挂起,对吗?这是一个可能的僵局。互联网上有足够多的关于这个领域的抱怨,我会严格按照 MSDN 的说明进行操作。无论如何,异步 I/O 肯定会更优雅?
    • 这可能是一个死锁,但我所看到的似乎是不同进程之间的“远处的怪异动作”。我想知道为什么。异步 I/O 大致相当于我现在所做的,但它可能更优雅。我会在星期一看看。
    • @jnylen - 我相信这将是一条很好的路线。一切顺利,让我知道进展如何。
    【解决方案2】:

    我认为问题不在于您的代码。阻塞呼叫可以出于多种原因解除阻塞,不仅仅是因为他们的任务已完成。

    我必须承认,我不了解 Windows,但在 Unix 世界中,当子进程完成时,会向父进程发送一个信号,这会将他从任何阻塞调用中唤醒。这将解除对父母期望的任何输入的读取。

    如果 Windows 的工作方式类似,我不会感到惊讶。无论如何,请阅读阻塞调用可能解除阻塞的原因。

    【讨论】:

    • 这是它应该工作的方式。任何阻塞调用在任务不再相关时不会解除阻塞的设计对我来说似乎很疯狂。在我的情况下,孩子完成了,但父母没有从阻塞呼叫中醒来。似乎某处存在错误。
    猜你喜欢
    • 1970-01-01
    • 2018-05-02
    • 1970-01-01
    • 2011-02-17
    • 2015-01-20
    • 2016-03-26
    相关资源
    最近更新 更多