Асинхронному Await модель .net 4.5 это парадигма меняется. Это слишком хорошо, чтобы быть правдой.
Я портировал некоторый код с большим количеством операций ввода-вывода в async-await, потому что блокировки остались в прошлом.
Довольно много людей сравнивают async-await с заражением зомби, и я обнаружил, что это довольно точно. Асинхронный код любит другой асинхронный код (вам нужна асинхронная функция, чтобы ожидать выполнения асинхронной функции). Так что все больше и больше функций становятся асинхронными, и это продолжает расти в вашей кодовой базе.
Изменение функций на асинхронные - несколько повторяющаяся и лишенная воображения работа. Добавьте async
ключевое слово в объявление, оберните возвращаемое значение, Task<>
и все готово. Довольно неприятно, насколько легко весь процесс, и довольно скоро сценарий замены текста автоматизирует для меня большую часть «портирования».
А теперь вопрос ... Если весь мой код медленно превращается в асинхронный, почему бы просто не сделать его асинхронным по умолчанию?
Очевидная причина, по которой я предполагаю, - это производительность. Async-await имеет свои накладные расходы и код, который не обязательно должен быть асинхронным, желательно не должен. Но если производительность является единственной проблемой, конечно, некоторые умные оптимизации могут автоматически удалить накладные расходы, когда они не нужны. Я читал об оптимизации "быстрого пути" , и мне кажется, что она одна должна решить большую часть этого.
Возможно, это сравнимо со сменой парадигмы, вызванной сборщиками мусора. На заре GC освобождение собственной памяти было определенно более эффективным. Но массы по-прежнему выбирают автоматический сбор в пользу более безопасного и простого кода, который может быть менее эффективным (и даже это, возможно, больше не соответствует действительности). Может, здесь так и должно быть? Почему не все функции должны быть асинхронными?
источник
async
(для выполнения некоторого контракта), это, вероятно, плохая идея. Вы получаете недостатки async (повышенная стоимость вызовов методов; необходимость использованияawait
в вызывающем коде), но ни одного из преимуществ.Ответы:
Прежде всего, спасибо за добрые слова. Это действительно потрясающая функция, и я рад, что был ее небольшой частью.
Что ж, вы преувеличиваете; весь ваш код не становится асинхронным. Когда вы складываете два «простых» целых числа вместе, вы не ждете результата. Когда вы складываете два будущих целых числа вместе, чтобы получить третье будущее целое число - потому что
Task<int>
это целое число, к которому вы собираетесь получить доступ в будущем - конечно, вы, вероятно, будете ждать результата.Основная причина не делать все асинхронным, заключается в том, что цель async / await - упростить написание кода в мире с множеством операций с высокой задержкой . Подавляющее большинство ваших операций не связаны с высокой задержкой, поэтому нет никакого смысла снижать производительность, уменьшающую эту задержку. Скорее, некоторые из ваших ключевых операций связаны с высокой задержкой, и эти операции вызывают заражение асинхронным кодом зомби.
Теоретически теория и практика похожи. На практике это не так.
Позвольте мне дать вам три очка против такого рода преобразований с последующим этапом оптимизации.
Первый момент снова: async в C # / VB / F # по сути является ограниченной формой передачи продолжения . Огромное количество исследований в сообществе функциональных языков было направлено на выяснение способов определения того, как оптимизировать код, который интенсивно использует стиль передачи продолжения. Команде компилятора, вероятно, придется решать очень похожие проблемы в мире, где "асинхронный" был по умолчанию, а неасинхронные методы должны были быть идентифицированы и деасинхронизированы. Команда C # на самом деле не заинтересована в решении открытых исследовательских задач, так что это большой аргумент против.
Второй аргумент против - то, что C # не имеет уровня "ссылочной прозрачности", который делает такие виды оптимизации более управляемыми. Под «ссылочной прозрачностью» я подразумеваю свойство, от которого значение выражения не зависит, когда оно оценивается . Выражения вроде
2 + 2
ссылочно прозрачны; вы можете выполнить оценку во время компиляции, если хотите, или отложить ее до времени выполнения и получить тот же ответ. Но такое выражение, какx+y
нельзя перемещать во времени, потому что x и y могут меняться со временем .Async значительно усложняет рассуждение о том, когда произойдет побочный эффект. Перед async, если вы сказали:
и
M()
былvoid M() { Q(); R(); }
, иN()
былvoid N() { S(); T(); }
, иR
иS
вызывать побочные эффекты, то вы знаете , что побочный эффект R, случается , прежде чем побочный эффект S в. Но если да,async void M() { await Q(); R(); }
то вдруг это вылетает из окна. У вас нет гарантии,R()
произойдет ли это до или послеS()
(если, конечно, этогоM()
не ждут; но, конечно, этогоTask
не нужно ждать до концаN()
).Теперь представьте, что это свойство больше не знать, в каком порядке происходят побочные эффекты, применяется ко всем частям кода в вашей программе, кроме тех, которые оптимизатору удается деасинхронизировать. По сути, вы больше не имеете ни малейшего представления о том, какие выражения будут оцениваться в каком порядке, а это означает, что все выражения должны быть ссылочно прозрачными, что сложно в таком языке, как C #.
Третий аргумент против заключается в том, что вы должны спросить: «Почему асинхронный режим такой особенный?» Если вы собираетесь утверждать, что каждая операция должна быть на самом деле,
Task<T>
тогда вы должны быть в состоянии ответить на вопрос «почему бы и нетLazy<T>
?». или "почему бы и нетNullable<T>
?" или "почему бы и нетIEnumerable<T>
?" Потому что мы могли бы сделать это так же легко. Почему не должно быть так, чтобы каждая операция была отменена до нулевого значения ? Либо каждая операция вычисляется лениво, а результат кэшируется для дальнейшего использования , либо результатом каждой операции является последовательность значений, а не одно значение . Затем вы должны попытаться оптимизировать те ситуации, когда вы знаете: «О, это никогда не должно быть нулевым, поэтому я могу сгенерировать лучший код» и так далее.Дело в том, что мне не ясно, что
Task<T>
на самом деле является настолько особенным, чтобы оправдать такую большую работу.Если вас интересуют подобные вещи, я рекомендую вам изучить функциональные языки, такие как Haskell, которые имеют гораздо более сильную ссылочную прозрачность и допускают все виды неупорядоченной оценки и автоматическое кэширование. Haskell также имеет гораздо более сильную поддержку в своей системе типов для видов «монадических лифтингов», о которых я говорил.
источник
async/await
потому что что-то, что скрыто за слоями абстракции, может быть асинхронным.Как вы сказали, производительность - одна из причин. Обратите внимание, что вариант «быстрого пути», с которым вы связались, действительно улучшает производительность в случае завершенной задачи, но по-прежнему требует гораздо больше инструкций и накладных расходов по сравнению с одним вызовом метода. Таким образом, даже при наличии «быстрого пути» вы добавляете много сложностей и накладных расходов с каждым вызовом асинхронного метода.
Обратная совместимость, а также совместимость с другими языками (включая сценарии взаимодействия) также могут стать проблематичными.
Другой вопрос сложности и намерения. Асинхронные операции добавляют сложности - во многих случаях функции языка скрывают это, но есть много случаев, когда создание методов
async
определенно усложняет их использование. Это особенно верно, если у вас нет контекста синхронизации, поскольку асинхронные методы могут легко вызвать неожиданные проблемы с потоками.Кроме того, существует множество подпрограмм, которые по своей природе не являются асинхронными. Это имеет больше смысла как синхронные операции. Например, принуждение
Math.Sqrt
к использованиюTask<double> Math.SqrtAsync
было бы нелепым, поскольку нет никаких причин для того, чтобы это было асинхронным. Вместо того , чтобыasync
протолкнуть ваше приложение, вы бы в конечном итоге сawait
propogating везде .Это также полностью нарушит текущую парадигму, а также вызовет проблемы со свойствами (которые, по сути, являются просто парами методов ... будут ли они также работать асинхронными?), И будет иметь другие последствия во всем дизайне фреймворка и языка.
Если вы выполняете много работы, связанной с вводом-выводом, вы, как правило, обнаружите, что
async
повсеместное использование - отличное дополнение, многие из ваших процедур будутasync
. Однако, когда вы начинаете выполнять работу, связанную с процессором, в общем, делать что-тоasync
на самом деле не очень хорошо - это скрывает тот факт, что вы используете циклы процессора под API, который кажется асинхронным, но на самом деле не обязательно действительно асинхронным.источник
await FooAsync()
проще чемFoo()
? И иногда вместо небольшого эффекта домино у вас постоянно появляется огромный эффект домино, и вы называете это улучшением?Помимо производительности - асинхронный режим может стоить производительности. На клиенте (WinForms, WPF, Windows Phone) это благо для производительности. Но на сервере или в других сценариях без пользовательского интерфейса вы платите за производительность. Вы, конечно же, не хотите использовать асинхронный режим по умолчанию. Используйте его, когда вам нужны преимущества масштабируемости.
Используйте его, когда находитесь в сладком месте. В других случаях - нет.
источник
Я считаю, что есть веская причина сделать все методы асинхронными, если они не нужны - расширяемость. Выборочное создание асинхронных методов работает только в том случае, если ваш код никогда не развивается, и вы знаете, что метод A () всегда привязан к ЦП (вы сохраняете его синхронизацию), а метод B () всегда привязан к вводу-выводу (вы отмечаете его как асинхронный).
Но что, если все изменится? Да, A () выполняет вычисления, но в какой-то момент в будущем вам пришлось добавить туда ведение журнала или отчет, или определяемый пользователем обратный вызов с реализацией, которая не может предсказать, или алгоритм был расширен и теперь включает не только вычисления ЦП, но и также какой-то ввод / вывод? Вам нужно будет преобразовать метод в асинхронный, но это сломает API, и все вызывающие абоненты в стеке также должны будут быть обновлены (и это могут быть даже разные приложения от разных поставщиков). Или вам нужно будет добавить асинхронную версию вместе с версией синхронизации, но это не имеет большого значения - использование версии синхронизации будет блокировать и, следовательно, вряд ли приемлемо.
Было бы замечательно, если бы существующий метод синхронизации можно было сделать асинхронным без изменения API. Но на самом деле у нас нет такой возможности, я считаю, и использование асинхронной версии, даже если она в настоящее время не нужна, - единственный способ гарантировать, что вы никогда не столкнетесь с проблемами совместимости в будущем.
источник