我想以控制台中的形式读取进程的输出(标准输出与一个流中的标准错误混合)。有什么办法可以做到吗?
我正在考虑使用
ProcessStartInfo.UseShellExecute = true;
但是我无法异步读取输出。如果我设置
process.ProcessStartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += new DataReceivedEventHandler(partialOutputHandler);
然后我可以读取标准输出(我可以对标准错误执行相同的操作),但我不知道如何模拟控制台的行为(stdout 和 stderr 的混合)。
这类似于Linux具有将标准错误流重定向到标准输出流的功能;怎么办?
你的意思是这样的吗?
SynchronizationContext _syncContext;
MyForm()
{
_syncContext = SynchronizationContext.Current;
}
void StartProcess()
{
using (var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "myProcess.exe",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
}
})
{
process.OutputDataReceived += (sender, args) => Display(args.Data);
process.ErrorDataReceived += (sender, args) => Display(args.Data);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit(); //you need this in order to flush the output buffer
}
}
void Display(string output)
{
_syncContext.Post(_ => myTextBox.AppendText(output), null);
}
MSDN 文章指出:
重定向的StandardError流可以同步读取或者 异步地。 Read、ReadLine 和 ReadToEnd 等方法执行 对进程的错误输出流进行同步读取操作。 这些同步读取操作直到关联的 进程写入其 StandardError 流,或关闭该流。
相反,BeginErrorReadLine 启动异步读取操作 标准错误流。该方法启用指定事件 流输出的处理程序并立即返回给调用者, 当流输出定向到时,它可以执行其他工作 事件处理程序。
同步读取操作在调用者之间引入了依赖关系 从 StandardError 流读取并且子进程写入 那条溪流。这些依赖性可能会导致死锁情况。 当调用者从子进程的重定向流中读取时, 这取决于孩子。调用者等待读操作 直到子进程写入流或关闭流。当 子进程写入足够的数据来填充其重定向流,它是 依赖于父母。子进程等待下一次写入 操作直到父级从完整流中读取或关闭 溪流。当调用者和子进程 进程互相等待完成操作,并且两者都不能 继续。您可以通过评估之间的依赖关系来避免死锁 调用者和子进程。
这同样适用于
StandardOutput
,因此您只需异步读取两个流。
Merging
两者都流入一个,这使得检测什么输出是错误报告以及什么是“产品”信息变得复杂。
我找到了答案:
输出流被缓冲。没有办法得到真实的 插入流中的项目的顺序。 事实上它 没有什么意义,因为两个流也可以同时写入 时间。 它们是相互独立的。 因此尽你所能 要做的就是在每个到达时获取它们的顺序输出。
通常这不是问题,因为几乎所有控制台应用程序都使用 输出和错误消息的标准输出。 错误流 某些应用程序使用但消息通常是重复的 输出流中生成错误。
来源:http://social.msdn.microsoft.com/Forums/uk/csharpgeneral/thread/192b6df7-9437-42cf-81c1-c125021735ba
类似的示例,但我为此目的使用 StringBuilder 将标准输出和错误收集到单独的字符串中。
/// <summary>
/// Executes command
/// </summary>
/// <param name="cmd">command to be executed</param>
/// <param name="output">output which application produced</param>
/// <param name="transferEnvVars">true - if retain PATH environment variable from executed command</param>
/// <returns>true if process exited with code 0</returns>
static bool ExecCmd(string cmd, out String output, bool transferEnvVars = false)
{
ProcessStartInfo processInfo;
Process process;
if (transferEnvVars)
cmd = cmd + " && echo --VARS-- && set";
processInfo = new ProcessStartInfo("cmd.exe", "/c " + cmd);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardError = true;
processInfo.RedirectStandardOutput = true;
process = Process.Start(processInfo);
// Executing long lasting operation in batch file will hang the process, as it will wait standard output / error pipes to be processed.
// We process these pipes here asynchronously.
StringBuilder so = new StringBuilder();
process.OutputDataReceived += (sender, args) => { so.AppendLine(args.Data); };
StringBuilder se = new StringBuilder();
process.ErrorDataReceived += (sender, args) => { se.AppendLine(args.Data); };
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
output = so.ToString();
String error = se.ToString();
if (transferEnvVars)
{
Regex r = new Regex("--VARS--(.*)", RegexOptions.Singleline);
var m = r.Match(output);
if (m.Success)
{
output = r.Replace(output, "");
foreach ( Match m2 in new Regex("(.*?)=([^\r]*)", RegexOptions.Multiline).Matches(m.Groups[1].ToString()) )
{
String key = m2.Groups[1].Value;
String value = m2.Groups[2].Value;
Environment.SetEnvironmentVariable(key, value);
}
}
}
if(error.Length != 0)
output += error;
int exitCode = process.ExitCode;
if (exitCode != 0)
Console.WriteLine("Error: " + output + "\r\n" + error);
process.Close();
return exitCode == 0;
}
Thread outputReaderThread = new(() =>
{
StreamReader outputReader = currentShell.AdbShellProcess.StandardOutput;
StreamReader errorReader = currentShell.AdbShellProcess.StandardError;
while (!outputReader.EndOfStream || !errorReader.EndOfStream)
{
if (!errorReader.EndOfStream)
{
string? errorLine = errorReader.ReadLine();
}
if (!outputReader.EndOfStream)
{
string? outputLine = outputReader.ReadLine();
}
}
});
outputReaderThread.IsBackground = true;
outputReaderThread.Start();
Вот как можно читать синхронно оба потока в той послежовательности как они приходят без гонки потоков!