В чем разница между Console.WriteLine и Console.Out.WriteLineAsync в асинхронном методе?

Однажды, когда я использовал фрагмент cw (введите cwTAB) в асинхронном методе, Visual Studio использовала await Console.Out.WriteLineAsync(); вместо исходного Console.WriteLine();. Сегодня я обновил Visual Studio до 17.10 и заметил, что фрагмент выдает Console.WriteLine(); в асинхронных методах.

Из-за такого поведения в прошлом я использовал await Console.Out.WriteLineAsync(); в асинхронных методах, поэтому сначала подумал, что это может быть ошибка. Но после реального тестирования я не обнаружил никаких проблем с использованием Console.WriteLine(); в асинхронных методах.

Я хотел бы спросить, есть ли разница между этими двумя методами? Если разницы нет, почему Visual Studio рекомендовала await Console.Out.WriteLineAsync(); в старых версиях?


Согласно комментариям @PanagiotisKanavos, если в коде используется большое количество задач, Console.Out.WriteLineAsync(); иногда работает быстрее, но в большинстве случаев производительность обоих практически одинакова.

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

Узким местом здесь является ввод-вывод через консоль, которая получает блокировки. Это плохое место для улучшения производительности.

Damien_The_Unbeliever 04.07.2024 08:31

Асинхронность заключается не в том, чтобы выполнять определенные действия быстрее, а в том, чтобы высвободить ресурсы для выполнения других задач вместо ожидания ввода-вывода (например, записи в поток консоли, который может быть заблокирован другими вызовами для записи в него). В большинстве случаев асинхронность будет медленнее из-за необходимых накладных расходов.

Karl-Johan Sjögren 04.07.2024 08:34

Я сосредоточен не только на выступлении. Мне любопытно, почему раньше VS производил такое преобразование, хотя их производительность аналогична, а теперь его удалили.

shingo 04.07.2024 08:44

Это не очень хороший тест, и результаты фальшивые. Во-первых, он запускает 200 async void делегатов, чего нельзя ожидать. Это означает, что Benchmark1 можно легко закончить до того, как строки будут написаны. Во-вторых, в консоль можно записать только одно, иначе вывод будет перепутан. Это ограничение налагается .NET и, подозреваю, самой ОС. Это не так, если доступ к консоли осуществляется через TextWriter или через поток.

Panagiotis Kanavos 04.07.2024 08:45
TextWriter.WriteLineAsync не пишет на вывод быстрее. Как и все асинхронные операции ввода-вывода, он позволяет избежать блокировки вызывающего потока во время выполнения операции ввода-вывода. В настольном приложении это означает, что поток пользовательского интерфейса может рисовать пользовательский интерфейс, а не зависать. В веб-приложении это означает, что поток может обрабатывать другие запросы. Необходимая синхронизация через обратные вызовы/порты завершения ввода-вывода означает, что асинхронные операции выполняются немного медленнее, чем синхронизация.
Panagiotis Kanavos 04.07.2024 08:49

Если вам нужен значимый тест, используйте await Parallel.ForAsync(0,200,...

Panagiotis Kanavos 04.07.2024 08:50

@PanagiotisKanavos Спасибо за ваши предложения. Наконец я получил тест с WriteLineAsync быстрее, чем WriteLine, но я пробовал время обслуживания, в большинстве случаев оно почти одинаковое. Означает ли это, что изменения, внесенные Visual Studio, хороши?

shingo 04.07.2024 10:15

О каком изменении вы говорите? Какой-то шаблон? Что-то другое? Visual Studio не генерирует консольный код сама по себе.

Panagiotis Kanavos 04.07.2024 10:18

@PanagiotisKanavos Фрагмент cw. В старой Visual Studio версии 17.6, если вы нажмете cw + tab в асинхронном методе, вы получите await Console....

shingo 04.07.2024 10:20

@TheodorZoulias Я хотел бы знать, есть ли они, но, основываясь на моем тестировании и комментариях выше, я уверен, что между ними нет большой разницы в производительности, поэтому мне любопытно, какие еще причины побудили VS порекомендовать мне используйте await Console.Out.WriteLineAsync в старых версиях.

shingo 04.07.2024 12:14

@shingo во-первых, я не говорил Console.Out.WriteLineAsync(); is sometimes faster совсем наоборот. Ожидание или использование любого обратного вызова влечет за собой штраф. Однако есть только одна консоль, и она в конечном итоге синхронизируется самой ОС. По этой причине меня больше интересовало бы, почему cw генерируется WriteLineAsync. Чтобы получить ответ на этот вопрос, потребуется поискать в вопросах GH или обсуждениях сообщества VS Dev. Проблема здесь не только в том, что VS не имеет открытого исходного кода, но и сама коллекция фрагментов не является функцией или артефактом кода. Если кто-то знает, это, наверное, Мэдс Кристенсен.

Panagiotis Kanavos 05.07.2024 11:51

Я подозреваю, что весь список фрагментов в какой-то момент был «асинхронизирован», но WriteLineAsync вскоре вызвал проблемы, поскольку люди поняли, что сама операционная система (Linux или Windows) синхронизирует их вызовы и, вероятно, приводит к перепутанному выводу. Фрагменты предназначены для распространенных случаев, а Console.Out.WriteLineAsync встречается нечасто. Особенно, если вам нужен хороший результат, а не просто дамп журналов. Проверьте Командная строка Windows: знакомство с псевдоконсолью Windows (ConPTY)

Panagiotis Kanavos 05.07.2024 12:07
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
12
187
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Если вы посмотрите декомпилированный исходный код Console.WriteLine, вы увидите

[MethodImpl(MethodImplOptions.NoInlining)]
public static void WriteLine() => Console.Out.WriteLine();

Итак, это в основном то же самое. Консоль поддерживает запись в основные потоки In, Out, Err. Я думаю, что Console.WriteLine — это просто удобная оболочка для записи в Out, потому что она наиболее часто используется.

Они не сравнивают Out.WriteLine с WriteLine, они сравнивают Out.WriteLine*Async*.

Damien_The_Unbeliever 04.07.2024 08:38

Декомпилировать код не нужно, исходник здесь -> github.com/dotnet/runtime/blob/…

phuzi 04.07.2024 09:48

С точки зрения производительности разница, скорее всего, незначительна, поскольку запись текста в консоль происходит очень быстро. Лично я бы сказал, что Console.WriteLine следует использовать вместо Console.Out.WriteLineAsync. Как показало тестирование, Console.Out.WriteLineAsync иногда работает быстрее на несколько микросекунд или около того, но не всегда.

Поскольку оба пишут в консоль, видимой разницы между Console.WriteLine и Console.Out.WriteLineAsync нет.

Свойство Console.Out — это статическое TextWriter, которое открывает дескриптор консоли и записывает туда текст. Вместо этого каждый метод Write и WriteAsync будет писать в консоль.

Когда вы вызываете метод Console.WriteLine, он вместо этого вызывает Console.Out.WriteLine, и поскольку каждый метод Write в свойстве Console.Out записывает непосредственно в консоль, он записывает в консоль новую строку.

Однако у TextWriter также есть методы .WriteLineAsync (помимо других методов асинхронной записи). Поскольку от TextWriter может наследовать что угодно, также должен быть способ писать текст асинхронно. Итак, Console.Out.WriteLineAsync просто выводит текст с новой строки на консоль и реализуется, потому что является частью TextWriter.

В общем, разницы скорее всего нет, так как оба пишут в консоль. .WriteLineAsync реализован, потому что он должен быть реализован, поскольку это метод, который переопределяет TextWriter.

В синхронных и асинхронных методах лично я бы рекомендовал использовать Console.WriteLine.

Помимо технических отличий, еще одним отличием является то, что Console.Out.WriteLineAsync не получил широкого распространения.

См. Методы TextWriter.

Проблема с фрагментом cw Visual Studio 2022

Насколько я понимаю, более старая версия Visual Studio 2022 вставляла await Console.Out.WriteLineAsync в асинхронный метод вместо Console.WriteLine, который изменился после обновления. Visual Studio 2022 действительно это делает (17.9.6).

Я не совсем понимаю, почему его удалили, потому что в Интернете, похоже, нет никакой информации. Я предполагаю, что удаление связано с тем, что await Console.Out.WriteLineAsync больше не используется широко и это почти то же самое, что и Console.WriteLine, но не уверен, почему VS рекомендовал его раньше.

Если вы хотите преобразовать все вызовы await Console.Out.WriteLineAsync в Console.WriteLine во всей базе кода (будь то проект или решение), нажмите Ctrl + Shift + H, чтобы открыть диалоговое окно «Заменить в файлах», в котором можно заменить await Console.Out.WriteLineAsync на Console.WriteLine.

Чего я не понимаю, так это того, что, поскольку они одинаковы, почему await Console.Out.WriteLineAsync рекомендовалось в предыдущей версии Visual Studio?

shingo 04.07.2024 15:15

@shingo Отредактировал ответ, включив эту информацию

winscripter 04.07.2024 16:29

Нет абсолютно никакой разницы между Console.WriteLine() и await Console.Out.WriteLineAsync(). Console.Out.WriteLineAsync всегда возвращает завершенное Task. Это всегда синхронная операция:

Task task = Console.Out.WriteLineAsync();
Console.WriteLine(task == Task.CompletedTask);

Выход:


True

Онлайн-демо.

Когда Console.Out.WriteLineAsync() возвращается, текст уже напечатан в консоли. Асинхронного измерения нет. Переключателя потока нет. Вся работа выполняется синхронно в текущем потоке.

Любая незначительная разница в производительности, которую вы могли заметить, может быть связана с незначительными ошибками в методологии тестирования или случайными факторами. Нет никаких причин, по которым один подход может быть быстрее другого. Оба делают одно и то же, совершенно одинаковым образом.

Чего я не понимаю, так это того, что, поскольку они одинаковы, почему await Console.Out.WriteLineAsync рекомендовалось в предыдущей версии Visual Studio, но было изменено на Console.WriteLine в последней версии?

shingo 04.07.2024 15:14

@shingo, честно говоря, понятия не имею. Я потратил слишком много времени в своей жизни, изменяя уровень серьезности различных диагностик .NET на «Нет», чтобы избавиться от визуальных намеков на то, что VS не нравится мой код.

Theodor Zoulias 04.07.2024 16:26
Ответ принят как подходящий

Я нашел реальный ответ, просмотрев исходный код на GitHub и осознав, что Console.Out.WriteLineAsync не имеет смысла (Out — это синхронизированный TextWriter). Последний поиск Console.Out.WriteLineAsync в Google после просмотра исходного кода вернул обсуждение в выпуске Semantic Snippets GH в репозитории Roslyn.

Его снова изменили на Console.WriteLine, потому что, как написал Стивен Тауб в обсуждении проблемы

Console.Out всегда является синхронизированным средством записи текста (TextWriter.Synchronized), при этом все операции охраняются монитором; все операции, записывающие данные в консоль, защищены одной и той же блокировкой, и каждый раз, когда мы пытались это изменить, нам приходилось откатывать назад, потому что значительное количество приложений взяли на себя зависимость от возможности делать такие вещи, как lock (Console.Out) { Console.ForegroundColor = ...; ... multiple writes ...; Console.ResetColor(); }. Асинхронные операции на таком записывающем устройстве преобразуются в синхронные. Использование await Console.Out.WriteLineAsync(...) вместо Console.WriteLine(...) не имеет никаких преимуществ, а только накладные расходы/более сложный код.

В выпуске Semantic Snippets GH neuec в 2023 году отметил, что WriteLineAsync не нужен:

В последней версии Visual Studio cw стал Console.Out.WriteLineAsync в асинхронном контексте, но я думаю, что в этом нет необходимости.

Это плохая функция, если ее не вернуть обратно к Console.WriteLine или не сделать настраиваемой.

Потому что

ConsoleStream имеет только синхронную запись. 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

Другими словами, я не думаю, что Async имеет смысл.

Далее он также указывает, что Console.Out — это синхронизированная программа записи текста, которая синхронно выполняет асинхронные операции и возвращает выполненную задачу.

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

Кстати, я никогда не писал, что асинхронность будет быстрее. Во всяком случае, это может быть немного медленнее из-за сгенерированного конечного автомата и обратных вызовов, если асинхронные операции действительно выполняются. Если код выполняется синхронно, мы платим стоимость конечного автомата без всякой выгоды. Это случай only overhead / more complicated code, о котором рассказывает Стивен Тауб.

Другие вопросы по теме