Search code examples
c#asynchronousconsoleconsole-application

What is the difference between Console.WriteLine and Console.Out.WriteLineAsync in an async method?


Once, when I used the cw snippet (type cwTAB) in an async method, Visual Studio would use await Console.Out.WriteLineAsync(); instead of the original Console.WriteLine();. Today I updated Visual Studio to 17.10 and noticed that the snippet produces Console.WriteLine(); in async methods.

Due to this behavior in the past, I have been using await Console.Out.WriteLineAsync(); in asynchronous methods, so initially I thought it might be a bug. But after actual testing, I did not find any problems with using Console.WriteLine(); in asynchronous methods.

I would like to ask, is there any difference between these two methods? If there is no difference, why did Visual Studio recommend await Console.Out.WriteLineAsync(); in old versions?


According to @PanagiotisKanavos' comments, if a large amount of tasks are used in the code, Console.Out.WriteLineAsync(); is sometimes faster, but in most cases, the performance of both is almost the same.

[Benchmark]
public async ValueTask Benchmark1()
{
    await Parallel.ForAsync(0, 50, async (i, ct) =>
    {
        await Console.Out.WriteLineAsync(LONGTEXT);
        await Job();
        await Console.Out.WriteLineAsync("short_text");
    });
}

[Benchmark]
public async ValueTask Benchmark2()
{
    await Parallel.ForAsync(0, 50, async (i, ct) =>
    {
        Console.WriteLine(LONGTEXT);
        await Job();
        Console.WriteLine("short_text");
    });
}

public async static ValueTask Job()
{
    for (int i = 0; i < 12950; i++)
        await Task.Yield();
}

Solution

  • I found the actual answer after going through the source in GitHub and realizing Console.Out.WriteLineAsync makes no sense (Out is a synchronized TextWriter). A last Google search for Console.Out.WriteLineAsync after going through the source returned a discussion in the Semantic Snippets GH issue in the Roslyn repository

    It was changed back to Console.WriteLine because as Stephen Toub wrote in the issue discussion

    Console.Out is always a synchronized text writer (TextWriter.Synchronized), with all operations guarded by a Monitor; all operations that write to the console are guarded by the same lock, and any time we've tried to change that, we've had to revert because significant numbers of apps have taken dependencies on being able to do things like lock (Console.Out) { Console.ForegroundColor = ...; ... multiple writes ...; Console.ResetColor(); }. Async operations on such a writer get converted into synchronous ones. Using await Console.Out.WriteLineAsync(...) instead of Console.WriteLine(...) has no upside and only overhead / more complicated code.

    In the Semantic Snippets GH issue neuecc pointed out in 2023 that WriteLineAsync is unnecessary:

    In the latest Visual Studio, cw has become Console.Out.WriteLineAsync at async context, but I think this is unnecessary.

    It is a bad feature unless it is reverted back to Console.WriteLine or made configurable.

    Because

    ConsoleStream only has sync write. https://source.dot.net/#System.Console/System/IO/ConsoleStream.cs,c0e0ba628fa4c224

    UnixConsoleStream: https://source.dot.net/#System.Console/System/ConsolePal.Unix.ConsoleStream.cs,13bf065fbb4646d5

    WindowsConsoleStream: https://github.com/dotnet/runtime/blob/f9f7f8079377039a8d42269434a41ca55d14d591/src/libraries/System.Console/src/System/ConsolePal.Windows.cs#L1076

    In other words, I don't think Async makes sense.

    Further down he also points out that Console.Out is a synchronized text writer that performs async operations synchronously and returns a completed task

    [MethodImpl(MethodImplOptions.Synchronized)]
    public override Task WriteLineAsync()
    {
        WriteLine();
        return Task.CompletedTask;
    }
    

    BTW I never wrote that async will be faster. If anything, it can be slightly slower due to the generated state machine and callbacks if async operations are actually performed. If the code executes synchronously, we pay the cost of the state machine for no benefit. It's the only overhead / more complicated code case that Stephen Toub talks about