Можно ли использовать async-wait, чтобы получить какие-либо выгоды от производительности?

Всякий раз, когда я читаю о async - await, пример использования всегда, где есть пользовательский интерфейс, который вы не хотите замораживать. Либо все книги/учебники по программированию одинаковы, либо блокировка пользовательского интерфейса - единственный случай async - await, о котором я должен знать в качестве разработчика.

Есть ли примеры использования async - await для повышения производительности в алгоритме? Например, возьмите любой из классических вопросов интервью с программированием:

  • Найдите ближайшего общего предка в двоичном дереве
  • Учитывая a[0], a[1],..., a[n-1], представляющие цифры номера базы-10, найдите следующее наибольшее число, которое использует те же цифры
  • Найдите медиану двух отсортированных массивов (т.е. медианное число, если вы должны их объединить)
  • Учитывая массив чисел 1, 2,..., n с отсутствием одного номера, найдите недостающее число
  • Найти наибольшие 2 числа в массиве

Есть ли способ сделать это с помощью async - await с преимуществами производительности? И если да, то что, если у вас только 1 процессор? Тогда не ваша машина просто делит свое время между задачами, а не делает их в одно и то же время?

Ответ 1

Кто-то здесь, в Stackoverflow (извините, больше не знаю его имени) однажды объяснил это следующим образом. Это объяснение помогло мне понять преимущества асинхронного ожидания

Дополнение: я нашел, кто написал аналогию Именно в этом интервью с Эрик Липперт. Искать где-то на полпути для асинхронного ожидания

Предположим, вы должны завтракать. Вам нужно поджарить немного хлеба и отварить яйца, возможно, приготовить чай?

Метод 1: синхронный. Выполняется одним потоком. Вы начинаете поджаривать хлеб. Подождите, пока хлеб не поджарится. Удалите хлеб. Начните кипящую воду, дождитесь, пока вода кипит и вставьте яйцо. Подождите, пока яйцо не будет готово и не удалите яйцо. Начните кипящую воду для чая. Подождите, пока вода кипит и сделайте чай.

Вы увидите все ожидания. в то время как поток ждет, он может делать другие вещи.

Способ 2: ожидание асинхронного вызова, еще один поток. Вы начинаете поджаривать хлеб. В то время как хлеб поджаривается, вы начинаете кипятить воду для яиц, а также для чая. Затем вы начинаете ждать. Когда любая из трех задач завершена, вы выполняете вторую часть задачи, в зависимости от того, какая задача была выполнена в первую очередь. Так что, если вода для яиц кипит первым, вы готовите яйца и снова ждите завершения любой задачи.

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

Легко видеть, что таким образом ваш завтрак будет готов в более короткие сроки (и ваш хлеб будет еще теплым!). В компьютерной жизни это происходит, когда вы делаете что-то медленное, например, записываете файл на диск, получаете информацию из базы данных или из Интернета. Обычно это те функции, в которых вы увидите асинхронную версию.

Дополнение: после некоторых замечаний других пользователей в другом месте и некоторых тестов я обнаружил, что на самом деле это может быть любой поток, который продолжает вашу работу после ожидания. Этот другой поток имеет один и тот же "контекст" и, следовательно, может действовать так, как если бы это был исходный поток.

Способ 3: Нанимать поваров для тостов хлеба и варить яйца, пока вы делаете чай: Настоящий асинхронный. Несколько потоков Это самый дорогой вариант, потому что он включает в себя создание отдельных потоков. В примере приготовления завтрака это, вероятно, не ускорит процесс, потому что относительно большие времена процесса вы ничего не делаете. Но если, например, вам также нужно нарезать помидоры, возможно, вам будет удобно сделать повар (отдельный поток), пока вы делаете другие вещи, используя асинхронный ожидание. Конечно, один из вас ждет, когда повар закончит свою нарезку.

Еще одна статья, которая объясняет многое, - это Async and Await, написанный когда-либо столь полезным Стивеном Клири.

Ответ 2

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

Это наиболее распространенный вариант использования для async. Другой - в серверных приложениях, где async может увеличить масштабируемость веб-серверов.

Есть ли примеры того, как можно использовать async-await для повышения производительности в алгоритме?

Нет.

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

Асинхронный код совершенно другой. Точка асинхронного кода заключается в том, чтобы не использовать текущий поток во время выполнения операции. Async-код, как правило, связан с I/O-привязкой или основан на событиях (например, таймер). Асинхронный код - это еще одна форма concurrency.

У меня есть async intro в моем блоге, а также сообщение на как async не использует потоки.

Обратите внимание, что задачи, используемые параллельной библиотекой задач, могут быть запланированы на потоки и будут выполнять код. Задачи, используемые Асинхронным шаблоном на основе задач, не имеют кода и не выполняются. Хотя оба типа задач представлены одним и тем же типом (Task), они создаются и используются совершенно по-разному; Я описываю эти Задачи делегата и обещаю задачи более подробно в своем блоге.

Ответ 3

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

Async/await "экономит время", только когда "задание" связано с I/O. Любое его применение к заданиям, связанным с CPU, приведет к некоторым результатам производительности. Это потому что, если у вас есть несколько вычислений, которые занимают, например, 10 секунд на вашем процессоре, то добавление async/await - то есть: создание задачи, планирование и синхронизация - просто добавит X дополнительное время к этим 10секундам, которые вам все равно нужно записать ваш CPU (ы), чтобы выполнить работу. Что-то близкое к идее закона Амдала. Не совсем так, но довольно близко.

Однако есть некоторые "но..".

Прежде всего, эта производительность часто возникает из-за введения async/await не так велика. (особенно, если вы стараетесь не переусердствовать).

Во-вторых, поскольку async/await позволяет вам значительно легче писать код с чередованием ввода-вывода, вы можете заметить новые возможности для удаления времени ожидания ввода-вывода в местах, где вы были бы слишком ленивы (:)), чтобы сделать это в противном случае или в тех местах, где это могло бы заставить код с трудом следовать без асинхронного/ожидающего синтаксиса. Например, разделение кода вокруг сетевых запросов является довольно очевидным, но вы можете заметить, что вы также можете обновить некоторые файлы ввода/вывода в тех немногих местах, где вы пишете файлы CSV или читаете файлы конфигурации и т.д. что выигрыш здесь не будет за счет async/await - это будет за счет перезаписи кода, который обрабатывает файл ввода/вывода. Вы можете сделать это без async/await тоже.

В-третьих, поскольку некоторые операционные системы i/o упрощают работу, вы можете заметить, что разгрузка загруженной ЦП работы на другую услугу или машину намного проще, что также может улучшить вашу воспринимаемую производительность (более короткое время настенных часов) но общее потребление ресурсов будет расти: добавлена ​​другая машина, потрачено время на сетевые операторы и т.д.

Четвертый: пользовательский интерфейс. Вы действительно не хотите его замораживать. Очень легко обернуть оба задания, связанные с I/O-привязкой и ЦП, в Задачи и асинхронно/ждут на них и поддерживать отзывчивость интерфейса. Вот почему вы его везде упоминаете. Однако, в то время как операции с I/O-привязкой в ​​идеале должны быть асинхронными вплоть до самого листа, чтобы удалить столько времени ожидания ожидания на всех длинных вводах-выводах, задания, связанные с CPU, не нужно разделить или асинхронно не больше, чем только 1 вниз. Наличие огромного монолитного задания вычисления, заключенного только в одну задачу, достаточно, чтобы разблокировать пользовательский интерфейс. Конечно, если у вас много процессоров/ядер, все равно стоит распараллеливать все возможное внутри, но в отличие от ввода-вывода - слишком много, и вы будете заняты переключением задач вместо того, чтобы пережевывать вычисления.

Подведение итогов: если у вас есть время ввода-вывода - операции async могут сэкономить много времени. Трудно переопределить асинхронные операции ввода-вывода. Если у вас есть операции с процессором, то добавление чего-либо будет потреблять больше времени процессора и больше памяти в целом, но время настенных часов может быть лучше благодаря разделению задания на более мелкие части, которые, возможно, могут быть запущены на других ядрах при одном и том же время. Это не сложно переусердствовать, поэтому вам нужно быть осторожным.

Ответ 4

Метод работает в текущем контексте синхронизации и использует время в потоке только тогда, когда метод активен. Вы можете использовать Task.Run для переместить работу, связанную с процессором, в фоновый поток, но фоновый поток не помогает в процессе, который просто ждет результатов, чтобы стать доступны.

Когда у вас есть один процессор и несколько потоков в приложении, ваш процессор переключается между потоками, чтобы имитировать параллельную обработку. С async/await ваша операция async не требует времени потока, таким образом, вы уделяете больше времени, чтобы другие потоки вашего приложения выполняли работу. Например, ваше приложение (не-пользовательский интерфейс) все равно может выполнять HTTP-вызовы, и все, что вам нужно, это просто ждать ответа. Это один из тех случаев, когда преимущество использования async/await велико.

Когда вы вызываете async DoJobAsync(), не забывайте .ConfigureAwait(false), чтобы получить лучшую производительность для приложений, отличных от UI, которые не нуждаются в объединении с контекстом потока пользовательского интерфейса.

Я не говорю о хорошем синтаксисе, который помогает сделать ваш код чистым.

MSDN

Ответ 5

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

Таким образом, по большей части вы не обнаружите, что данная операция улучшает производительность, но может показаться, что интенсивное использование улучшило производительность.

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

Это отличается от параллельных операций с привязкой к ЦП, которые (независимо от того, выполняются ли они с помощью задач или иным образом), как правило, масштабируются только до количества доступных ядер. (Гипер-резьбовые сердечники ведут себя как 2 или более ядра в некоторых отношениях, а не в других).

Ответ 6

Асинхронные и ожидающие ключевые слова не создают дополнительные потоки. Асинхронные методы не требуют многопоточности, потому что метод async не запускается в своем потоке. Метод работает в текущем контексте синхронизации и использует время в потоке только тогда, когда метод активен. Вы можете использовать Task.Run для перемещения работы, связанной с процессором, в фоновый поток, но фоновый поток не помогает процессу, который просто ждет, когда результаты станут доступными.

Функция async-ожидания .NET не отличается от функции других фреймворков. Это не дает преимущества производительности, но позволяет просто осуществлять непрерывное переключение между задачами в одном потоке вместо того, чтобы одна задача блокировала поток. Если вы хотите повысить производительность, используйте параллельную библиотеку задач.

Посетите https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx