Однажды, когда я использовал фрагмент 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
, о котором рассказывает Стивен Тауб.
Узким местом здесь является ввод-вывод через консоль, которая получает блокировки. Это плохое место для улучшения производительности.