Windows下.NET 8进程输出重定向:Console.Write+Flush与WriteLine行为差异问题
我在Windows 11系统下有一个.NET 8.0可执行程序,用于启动另一个可执行程序并监听其标准输出。当前代码在子进程使用Console.WriteLine时能正常工作,但使用Console.Write + Flush时却无法触发输出回调——我原本以为这两种输出方式是等效的。
父进程代码
public static int Main(string[] args) { var executable = @"hardcoded path to childprocess.dll"; var psi = new ProcessStartInfo(executable) { RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true }; using var process = Process.Start(psi)!; process.OutputDataReceived += (_, dataReceivedEventArgs) => ProcessOnOutputDataReceived(dataReceivedEventArgs, Console.Out); process.BeginOutputReadLine(); process.WaitForExit(); return process.ExitCode; } private static void ProcessOnOutputDataReceived(DataReceivedEventArgs args, TextWriter textWriter) { // never called for Console.Write + Flush // called without issues with Console.WriteLine if (args.Data is var data and not null) { textWriter.WriteLine(data); } }
子进程代码
public static void Main(string[] args) { Console.Write("Output never visible :("); Console.Out.Flush(); // Console.WriteLine("This works perfectly fine!"); Console.ReadLine(); // just to stop the process from quitting. }
调试细节
调试时发现_autoFlush已启用,因此甚至不需要显式调用Flush。执行Flush时会进入ConsolePal::WriteFileNative,其中useFileAPIs设为true,最终调用Kernel32.WriteFile且返回了正确的写入字符数。调用FlushFileBuffers传入文件句柄也没有任何效果。
问题原因与解决方法
原因
BeginOutputReadLine()是基于行的异步监听机制,它会等待输出流中出现换行符(\n或\r\n)时,才会将当前内容作为一行触发OutputDataReceived事件。Console.WriteLine会自动在输出内容末尾追加换行符,因此能被正常检测到;而Console.Write仅写入内容本身,即使手动Flush,因为没有换行符,BeginOutputReadLine()不会将未完成的行推送出去。
解决方法
方法1:子进程输出时追加换行符
在Console.Write的内容末尾手动添加换行符,即使不调用Flush(因为_autoFlush已启用),也能触发父进程的回调:
Console.Write("Output now visible!\n");
方法2:父进程改用非行依赖的读取方式
放弃BeginOutputReadLine(),直接读取StandardOutput的流内容,比如使用异步字符缓冲区读取:
// 替换父进程中OutputDataReceived和BeginOutputReadLine的代码 _ = Task.Run(async () => { var buffer = new char[1024]; int charsRead; while ((charsRead = await process.StandardOutput.ReadAsync(buffer, 0, buffer.Length)) > 0) { Console.Write(new string(buffer, 0, charsRead)); } });
也可以使用一次性读取全部输出的方式(适合子进程结束后获取完整内容):
_ = Task.Run(async () => { string content = await process.StandardOutput.ReadToEndAsync(); Console.Write(content); });
内容的提问来源于stack exchange,提问作者Voo

