Этот вопрос касается языка C #, но я ожидаю, что он охватит другие языки, такие как Java или TypeScript.
Microsoft рекомендует лучшие практики по использованию асинхронных вызовов в .NET. Среди этих рекомендаций давайте выберем две:
- измените сигнатуру асинхронных методов, чтобы они возвращали Task или Task <> (в TypeScript это будет Promise <>)
- изменить имена асинхронных методов на xxxAsync ()
Теперь, при замене низкоуровневого синхронного компонента на асинхронный, это влияет на весь стек приложения. Поскольку async / await оказывает положительное влияние только в том случае, если используется «до конца», это означает, что имена сигнатур и методов каждого уровня в приложении должны быть изменены.
Хорошая архитектура часто предполагает размещение абстракций между каждым уровнем, так что замена компонентов нижнего уровня другими невидима компонентами верхнего уровня. В C # абстракции принимают форму интерфейсов. Если мы представляем новый низкоуровневый асинхронный компонент, каждый интерфейс в стеке вызовов должен быть либо изменен, либо заменен новым интерфейсом. Способ решения проблемы (асинхронный или синхронный) в реализующем классе больше не скрыт (абстрагирован) для вызывающих. Вызывающие абоненты должны знать, является ли это синхронизацией или асинхронностью.
Разве асинхронные / не ждут лучших практик, противоречащих принципам «хорошей архитектуры»?
Означает ли это, что каждому интерфейсу (скажем, IEnumerable, IDataAccessLayer) требуется свой асинхронный аналог (IAsyncEnumerable, IAsyncDataAccessLayer), чтобы их можно было заменить в стеке при переключении на асинхронные зависимости?
Если мы продвинем проблему чуть дальше, не будет ли проще предположить, что каждый метод является асинхронным (для возврата Task <> или Promise <>), и чтобы методы синхронизировали асинхронные вызовы, когда они на самом деле не являются асинхронный? Это то, что можно ожидать от будущих языков программирования?
источник
CancellationToken
, а те, которые это делают, могут захотеть указать значение по умолчанию). Удаление существующих методов синхронизации (и упреждающее разрушение всего кода), очевидно, не является началом.Ответы:
Какого цвета ваша функция?
Вы можете быть заинтересованы в том, какой цвет у Боба Нистрома. 1 .
В этой статье он описывает вымышленный язык, где:
Хотя это и вымышлено, это происходит довольно регулярно в языках программирования:
this
.Как вы уже поняли, из-за этих правил красные функции имеют тенденцию распространяться вокруг базы кода. Вы вставляете один, и постепенно он колонизирует всю базу кода.
1 Боб Нистром, помимо ведения блога, также является частью команды Dart и написал эту маленькую серию Crafting Interpreters; настоятельно рекомендуется для любого языка программирования / компилятора afficionado.
2 Не совсем верно, так как вы можете вызывать асинхронную функцию и блокировать, пока она не вернется, но ...
Ограничение языка
По сути, это ограничение языка / времени выполнения.
Например, язык с многопоточностью M: N, такой как Erlang и Go, не имеет
async
функций: каждая функция потенциально асинхронна, и ее «волокно» будет просто приостановлено, заменено и заменено обратно, когда оно снова будет готово.В C # использовалась модель потоков 1: 1, и поэтому было решено использовать синхронность в языке, чтобы избежать случайной блокировки потоков.
При наличии языковых ограничений руководящие принципы кодирования должны адаптироваться.
источник
async
); действительно, это был довольно распространенный шаблон для создания адаптивного пользовательского интерфейса. Это было так же красиво, как Эрланг? Нет. Но это все еще далеко от C :)Вы правы, здесь есть противоречие, но это не «лучшие практики», а плохие. Это потому, что асинхронная функция делает вещи существенно отличающиеся от синхронной. Вместо ожидания результата от его зависимостей (обычно некоторого ввода-вывода) он создает задачу для обработки главным циклом событий. Это не та разница, которая может быть хорошо спрятана под абстракцией.
источник
async
методы в C # для предоставления синхронных контрактов, если хотите. Единственная разница в том, что Go по умолчанию асинхронный, а C # по умолчанию синхронный.async
дает вам вторую модель программирования -async
это абстракция (что она на самом деле делает, зависит от времени выполнения, планировщика задач, контекста синхронизации, реализации ожидающего ...).Асинхронный метод ведет себя не так, как синхронный, как я уверен, вы знаете. Во время выполнения преобразовать асинхронный вызов в синхронный тривиально, но нельзя сказать обратное. Поэтому возникает логика: почему бы нам не создать асинхронные методы для каждого метода, который может этого потребовать, и позволить вызывающей стороне «преобразовать», по мере необходимости, в синхронный метод?
В некотором смысле это похоже на метод, который генерирует исключения, а другой - «безопасный» и не генерирует даже в случае ошибки. В какой момент кодер чрезмерно предоставляет эти методы, которые в противном случае могут быть преобразованы один в другой?
В этом есть две школы мысли: одна заключается в создании нескольких методов, каждый из которых вызывает другой, возможно, закрытый метод, допускающий возможность предоставления необязательных параметров или незначительных изменений в поведении, таких как асинхронность. Другой заключается в том, чтобы свести к минимуму методы интерфейса к базовым элементам, оставляя вызывающей стороне самостоятельно выполнять необходимые изменения.
Если вы учитесь в первой школе, есть определенная логика, чтобы посвятить класс синхронным и асинхронным вызовам, чтобы избежать удвоения каждого вызова. Microsoft склоняется в пользу этой школы мысли, и по соглашению, чтобы оставаться в соответствии со стилем, который предпочитает Microsoft, вам также придется иметь версию Async, почти так же, как интерфейсы почти всегда начинаются с «I». Позвольте мне подчеркнуть, что это не так , как таковое, потому что лучше сохранять согласованный стиль в проекте, а не делать это «правильным образом» и радикально менять стиль для разработки, которую вы добавляете в проект.
Тем не менее, я склоняюсь в пользу второй школы, которая заключается в минимизации методов интерфейса. Если я думаю, что метод может быть вызван асинхронным способом, метод для меня является асинхронным. Вызывающий может решить, стоит ли ждать завершения этой задачи, прежде чем продолжить. Если этот интерфейс является интерфейсом с библиотекой, есть более разумный способ сделать это таким образом, чтобы минимизировать количество методов, которые вам нужно исключить или настроить. Если интерфейс предназначен для внутреннего использования в моем проекте, я добавлю метод для каждого необходимого вызова во всем проекте для предоставленных параметров и без «дополнительных» методов, и даже тогда, только если поведение метода еще не охвачено существующим методом.
Однако, как и многие вещи в этой области, это в значительной степени субъективно. Оба подхода имеют свои плюсы и минусы. Microsoft также начала соглашение о добавлении букв, указывающих на тип в начале имени переменной, и «m_», чтобы указать, что это член, что приводит к таким именам переменных, как
m_pUser
. Я хочу сказать, что даже Microsoft не является непогрешимой и тоже может совершать ошибки.Тем не менее, если ваш проект следует этому асинхронному соглашению, я бы посоветовал вам соблюдать его и продолжать стиль. И только после того, как вы получите собственный проект, вы можете написать его так, как считаете нужным.
источник
.Wait()
метода и т.п. может привести к негативным последствиям, а в js, насколько я знаю, это вообще невозможно.Promise.resolve(x)
а затем добавите к нему обратные вызовы, эти обратные вызовы не будут выполнены немедленно.Давайте представим, что есть способ, позволяющий вам вызывать функции асинхронно без изменения их сигнатуры.
Это было бы здорово, и никто бы не порекомендовал вам сменить их имена.
Но фактические асинхронные функции, не только те, которые ожидают другую асинхронную функцию, но и самый низкий уровень, имеют некоторую структуру, специфичную для их асинхронной природы. например
Это те две части информации, которые нужны вызывающему коду для выполнения кода асинхронным способом, который вещи любят
Task
иasync
инкапсулируют.Существует более одного способа выполнения асинхронных функций,
async Task
это всего лишь один шаблон, встроенный в компилятор через возвращаемые типы, чтобы вам не приходилось вручную связывать событияисточник
Я остановлюсь на главном вопросе в меньшей степени на C # ness и более обобщенно:
Я бы сказал, что все зависит от того, какой выбор вы сделаете при разработке своего API и что вы дадите пользователю.
Если вы хотите, чтобы одна функция вашего API была только асинхронной, мало интересует соблюдение соглашения об именах. Просто всегда возвращайте Task <> / Promise <> / Future <> / ... как возвращаемый тип, это самодокументирование. Если он хочет получить синхронизированный ответ, он все равно сможет сделать это, ожидая, но если он всегда это делает, то это образцовый шаблон.
Однако, если вы выполняете только синхронизацию API, это означает, что если пользователь хочет, чтобы он был асинхронным, он должен сам управлять асинхронной частью.
Это может сделать довольно много дополнительной работы, однако это также может дать больше контроля пользователю о том, сколько одновременных вызовов он разрешает, время ожидания, повторные попытки и так далее.
В большой системе с огромным API-интерфейсом реализация большинства из них для синхронизации по умолчанию может быть проще и эффективнее, чем независимое управление каждой частью вашего API, особенно если они совместно используют ресурсы (файловая система, процессор, база данных и т. Д.).
Фактически для самых сложных частей вы могли бы идеально иметь две реализации одной и той же части вашего API, одна синхронно выполняет полезные вещи, одна асинхронная, полагаясь на синхронную, обрабатывает вещи и управляет только параллелизмом, нагрузками, тайм-аутами и повторными попытками. ,
Может быть, кто-то еще может поделиться своим опытом с этим, потому что у меня нет опыта работы с такими системами.
источник