如何解决在子进程的重定向 STDOUT 中缓冲
这样的代码可以托管一个控制台应用程序并监听其输出到 STDOUT 和 STDERR
Process process = new Process();
process.StartInfo.FileName = exePath;
process.StartInfo.UseShellExecute = false;
process.StartInfo.WorkingDirectory = context.WorkingDirectory;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = true; // if you don't and it reads,no more events
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = false;
process.EnableRaisingEvents = true;
process.ErrorDataReceived += (sender,dataReceivedEventArgs) =>
{
lastbeat = DateTime.UtcNow;
if (dataReceivedEventArgs.Data != null)
{
if (dataReceivedEventArgs.Data.EndsWith("%"))
{
context.Logger.Information($" PROGRESS: {dataReceivedEventArgs.Data}");
}
else
{
msg.Append(" STDERR (UNHANDLED EXCEPTION): ");
msg.AppendLine(dataReceivedEventArgs.Data);
success = false;
}
}
};
process.OutputDataReceived += (sender,dataReceivedEventArgs) =>
{
lastbeat = DateTime.UtcNow;
if (dataReceivedEventArgs.Data != null)
{
if (dataReceivedEventArgs.Data.EndsWith("%"))
{
context.Logger.Information($" PROGRESS: {dataReceivedEventArgs.Data}");
}
else
{
context.Logger.Information($" STDOUT: {dataReceivedEventArgs.Data}");
}
}
};
lastbeat = DateTime.UtcNow;
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
// wait for the child process,kill it if hearbeats are too slow
while (!process.HasExited)
{
Thread.Sleep(100);
var elapsed = DateTime.UtcNow - lastbeat;
if (elapsed.TotalSeconds > heartbeatIntervalSeconds * 3)
{
success = false;
msg.AppendLine("MODULE HEARTBEAT STOPPED,TERMINATING.");
try
{
process.Kill(entireProcessTree: true); // ...and your children's children
}
catch (Exception ek)
{
msg.AppendLine(ek.Message);
}
}
}
if (success)
{
process.Dispose();
context.Logger.Debug("MODULE COMPLETED");
return JobStepResult.Success;
}
else
{
process.Dispose();
context.Logger.Debug("MODULE ABORTED");
throw new Exception(msg.ToString());
}
托管进程可能会运行很长时间,因此我们发明了心跳机制。这里有一个约定,STDERR 用于带外通信,以便 STDOUT 不会被心跳消息污染。写入 STDERR 的任何以百分号结尾的文本行都被视为心跳,其他一切都是正常的错误消息。
我们有两个托管模块,其中一个模块与及时收到的心跳完美配合,但另一个似乎挂起,直到它在 STDERR 和 STDOUT 上的所有输出大量到达。
托管模块是用 Lahey FORTRAN 编写的。我看不到该代码。我已经向作者建议她可能需要刷新她的输出流或可能使用任何等效于 Thread.Sleep(10);
然而,问题在我这边并非不可能。当模块在控制台中手动执行时,它们的输出会以稳定的速度出现,并及时出现心跳消息。
是什么控制捕获的流的行为?
- 它们被缓冲了吗?
- 有什么方法可以影响这一点吗?
这可能是相关的。 Get Live output from Process
看来(见评论)这是一个老问题。我将我的托管代码提取到控制台应用程序中,问题也很明显。
代码
当托管的控制台应用程序是 dotnet 核心应用程序时,不会发生这种情况。据推测,dotnet 核心应用程序使用 ConPTY,因为这样它们就可以跨平台工作。
解决方法
这是一个老问题。应用程序可以检测它们是否在控制台中运行,如果不是,则选择缓冲它们的输出。例如,每当您调用 printf
时,Microsoft C 运行时都会故意执行此操作。
应用程序不应缓冲对 stderr 的写入,因为在您的程序有机会崩溃并擦除任何缓冲区之前,错误应该可用。但是,没有什么可以强制执行该规则。
这个问题有旧的解决方案。通过创建一个离屏控制台,您可以检测何时将输出写入控制台缓冲区。 Code Project 上有一篇文章更详细地讨论了这个问题和解决方案。
最近,Microsoft 对其控制台基础架构进行了现代化改造。如果您写入现代控制台,您的输出将转换为带有嵌入式 VT 转义序列的 UTF-8 流。世界其他地区几十年来一直在使用的标准。有关更多详细信息,您可以阅读他们关于该作品的博客系列 here。
我相信应该可以构建一种新的现代解决方法。一个存根进程,类似于上面的代码项目链接,它使用这些新的 pseudo console API 来启动一个子进程,捕获控制台 I/O 和管道,将无缓冲输出到其自己的 stdio 句柄。如果这样的存根进程分布在 Windows 中或由其他第三方发布,我会很好,但我还没有找到。
如果您想创建自己的存根,github.com/microsoft/terminal 中有几个示例是一个很好的起点。
但是我相信使用此解决方案或旧的解决方法仍会将输出和错误流合并在一起。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。