Search code examples
c#.net-core.net-8.0

Console.Write+Flush different behavior to Console.WriteLine


I have a .NET 8.0 executable under Windows 11 that starts another executable and then listens for its output:

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);
    }
}

This works fine if the child process uses Console.WriteLine, but I can't get it to work with Console.Write + Flush, which I'd imagine would be equivalent.

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.
}

Debugging the code I see that _autoFlush is enabled anyhow, so I shouldn't even need the explicit Flush afterwards.

For the flush we end up in ConsolePal::WriteFileNative with useFileAPIs set to true which means we end up calling Kernel32.WriteFile which returns the correctly written number of characters.

Calling FlushFileBuffers with the file handle makes no difference either.


Solution

  • BeginOutputReadLine in the parent process waits for a newline to complete the line and trigger the OutputDataReceived event. Without a newline, the OutputDataReceived event will not be raised.

    You need a different approach to read the output from the console. One way could be using unbuffered reads like:

    var psi = new ProcessStartInfo(executable)
    {
        RedirectStandardOutput = true,
        UseShellExecute = false,
        CreateNoWindow = true
    };
    
    using var process = Process.Start(psi)!;
    using var reader = process.StandardOutput;
    int nextChar;
    while ((nextChar = reader.Read()) != -1)
    {
        char c = (char)nextChar;
        Console.Write(c); // Process or write each character as it arrives
    }
    
    process.WaitForExit();