【发布时间】:2021-06-01 11:11:28
【问题描述】:
这样的代码可以托管一个控制台应用程序并监听其输出到 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 编写的。我对该代码没有可见性。我已经向作者建议她可能需要刷新她的输出流或可能使用 FORTRAN 等效于 Thread.Sleep(10);
但是,问题出在我这边并非不可能。当模块在控制台中手动执行时,它们的输出以稳定的速度出现,心跳消息及时出现。
什么控制着捕获的流的行为?
- 它们被缓冲了吗?
- 有什么方法可以影响这个吗?
这可能是相关的。 Get Live output from Process
似乎(见 cmets)这是一个老问题。我将托管代码提取到控制台应用程序中,问题也很明显。
Codiçil
当托管控制台应用程序是 dotnet 核心应用程序时,不会发生这种情况。大概 dotnet 核心应用程序使用 ConPTY,因为这样它们可以跨平台工作。
【问题讨论】:
-
这是一个老问题,有老套的变通方法。 codeproject.com/Articles/16163/…
-
嗯,也许这个老问题有新的解决方案devblogs.microsoft.com/commandline/…
-
@JeremyLakeman 我在这个问题上找不到任何关于 SO 的信息。我相信你是对的。你为什么不写一个简短的答案让我接受?只需几句话解释重定向管道的处理方式不同,您要么必须伪造控制台,要么在源程序中刷新缓冲区,并提供更多信息的链接。
标签: c# process io-redirection