Однажды, когда я использовал фрагмент 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();
}
Асинхронность заключается не в том, чтобы выполнять определенные действия быстрее, а в том, чтобы высвободить ресурсы для выполнения других задач вместо ожидания ввода-вывода (например, записи в поток консоли, который может быть заблокирован другими вызовами для записи в него). В большинстве случаев асинхронность будет медленнее из-за необходимых накладных расходов.
Я сосредоточен не только на выступлении. Мне любопытно, почему раньше VS производил такое преобразование, хотя их производительность аналогична, а теперь его удалили.
Это не очень хороший тест, и результаты фальшивые. Во-первых, он запускает 200 async void делегатов, чего нельзя ожидать. Это означает, что Benchmark1 можно легко закончить до того, как строки будут написаны. Во-вторых, в консоль можно записать только одно, иначе вывод будет перепутан. Это ограничение налагается .NET и, подозреваю, самой ОС. Это не так, если доступ к консоли осуществляется через TextWriter или через поток.
TextWriter.WriteLineAsync не пишет на вывод быстрее. Как и все асинхронные операции ввода-вывода, он позволяет избежать блокировки вызывающего потока во время выполнения операции ввода-вывода. В настольном приложении это означает, что поток пользовательского интерфейса может рисовать пользовательский интерфейс, а не зависать. В веб-приложении это означает, что поток может обрабатывать другие запросы. Необходимая синхронизация через обратные вызовы/порты завершения ввода-вывода означает, что асинхронные операции выполняются немного медленнее, чем синхронизация.
Если вам нужен значимый тест, используйте await Parallel.ForAsync(0,200,...
@PanagiotisKanavos Спасибо за ваши предложения. Наконец я получил тест с WriteLineAsync быстрее, чем WriteLine, но я пробовал время обслуживания, в большинстве случаев оно почти одинаковое. Означает ли это, что изменения, внесенные Visual Studio, хороши?
О каком изменении вы говорите? Какой-то шаблон? Что-то другое? Visual Studio не генерирует консольный код сама по себе.
@PanagiotisKanavos Фрагмент cw. В старой Visual Studio версии 17.6, если вы нажмете cw + tab в асинхронном методе, вы получите await Console....
@TheodorZoulias Я хотел бы знать, есть ли они, но, основываясь на моем тестировании и комментариях выше, я уверен, что между ними нет большой разницы в производительности, поэтому мне любопытно, какие еще причины побудили VS порекомендовать мне используйте await Console.Out.WriteLineAsync в старых версиях.
@shingo во-первых, я не говорил Console.Out.WriteLineAsync(); is sometimes faster совсем наоборот. Ожидание или использование любого обратного вызова влечет за собой штраф. Однако есть только одна консоль, и она в конечном итоге синхронизируется самой ОС. По этой причине меня больше интересовало бы, почему cw генерируется WriteLineAsync. Чтобы получить ответ на этот вопрос, потребуется поискать в вопросах GH или обсуждениях сообщества VS Dev. Проблема здесь не только в том, что VS не имеет открытого исходного кода, но и сама коллекция фрагментов не является функцией или артефактом кода. Если кто-то знает, это, наверное, Мэдс Кристенсен.
Я подозреваю, что весь список фрагментов в какой-то момент был «асинхронизирован», но WriteLineAsync вскоре вызвал проблемы, поскольку люди поняли, что сама операционная система (Linux или Windows) синхронизирует их вызовы и, вероятно, приводит к перепутанному выводу. Фрагменты предназначены для распространенных случаев, а Console.Out.WriteLineAsync встречается нечасто. Особенно, если вам нужен хороший результат, а не просто дамп журналов. Проверьте Командная строка Windows: знакомство с псевдоконсолью Windows (ConPTY)





Если вы посмотрите декомпилированный исходный код Console.WriteLine, вы увидите
[MethodImpl(MethodImplOptions.NoInlining)]
public static void WriteLine() => Console.Out.WriteLine();
Итак, это в основном то же самое. Консоль поддерживает запись в основные потоки In, Out, Err. Я думаю, что Console.WriteLine — это просто удобная оболочка для записи в Out, потому что она наиболее часто используется.
Они не сравнивают Out.WriteLine с WriteLine, они сравнивают Out.WriteLine*Async*.
Декомпилировать код не нужно, исходник здесь -> github.com/dotnet/runtime/blob/…
С точки зрения производительности разница, скорее всего, незначительна, поскольку запись текста в консоль происходит очень быстро. Лично я бы сказал, что 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.
Насколько я понимаю, более старая версия 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 Отредактировал ответ, включив эту информацию
Нет абсолютно никакой разницы между 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, честно говоря, понятия не имею. Я потратил слишком много времени в своей жизни, изменяя уровень серьезности различных диагностик .NET на «Нет», чтобы избавиться от визуальных намеков на то, что VS не нравится мой код.
Я нашел реальный ответ, просмотрев исходный код на 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, о котором рассказывает Стивен Тауб.
Узким местом здесь является ввод-вывод через консоль, которая получает блокировки. Это плохое место для улучшения производительности.