Comments 17
Удивительно, что в 2025м до сих пор где-то проблемы с асинхронностью. В той же Scala еще в 2015м нормальный, но не асинхронный фреймворк еще надо было поискать. И примерно тогда же появились чисто асинхронные коннекторы к Postgres-MySQL. Плюс есть async/await макросы (библиотека). В C++ уже много лет как корутины, и так же асинхронные коннекторы к Postgres-MySQL появились в userver.
С другой стороны, большинство веб-приложений ходят в базу. А большинство баз (RDBMS) обрабатывают соединения синхронно, поток на соединение или процесс на соединение. Асинхронные только всякий NoSQL, типа HBase/Mongo/Cassandra, и облачные базы типа DynamoDB. Так что если вы в асинхронном приложении обращаетесь к синхронной базе, выигрыш у вас будет символический, т.к. затык будет на стороне базы.
Всё-таки в продакшне чаще для СУБД используется отдельный сервер, так что выигрыш для самого приложения, которое отделено от сервера БД, вполне возможен.
Нет никаких явных проблем. Просто есть мнение, что можно сделать лучше чем сейчас. Учитывая что асинхронность в C# довольно давно, за эти годы появились идеи.
Удивительно, что в 2025м до сих пор где-то проблемы с асинхронностью. В той же Scala еще в 2015м нормальный, но не асинхронный фреймворк еще надо было поискать. И примерно тогда же появились чисто асинхронные коннекторы к Postgres-MySQL. Плюс есть async/await макросы (библиотека).
Не очень понятно про что Вы. Если про C# то это некорректно. Высокоуровневая работа с асинхронностью пришла в C# в 2010 году с TPL а уже в 2012 в нем придумали новую концепцию async/await которая повлияла на большинство мейнстримных языков. Так что C# в этом плане пионер.
А вот что удивительно действительно, это то что такой популярный язык как Java затянул до 2023 года просто игнорируя эту проблему.
Спасибо за отличную статью.
Таким образом, в Go получилось избавиться от разноцветных функций и при этом реализовать современный и эффективный механизм асинхронности.
Я бы заметил что при всех плюсах данного подхода - вышеупомянутого избавления от цветных функций, мой опыт показывает что обратная сторона - это неудобство работы с параллельными запросами и там где нужно задавать последовательности. Если в C# достаточно просто сделать то в Go это превращается в городьбу из каналов WaitGroups и так далее, раз за разом. Плюс, я до конца не уверен, хорошо это или плохо что я не знаю блокирующий вызов функции или нет. К примеру я вызываю функцию у которой под капотом системный вызов не сетевой природы и это стоит мне нового потока, а я бы хотел знать что функция будет создавать отдельный поток и грузить приложения.
Можно заметить общий механизм между Golang и Project Loom в JVM, который заключается в перехвате рантаймом блокирующих операций и выполнение их в неблокирующей манере. Если в Go это было реализовано изначально, то в JVM подобные масштабные изменения заняли 5 лет:
При всем тот что в Java худо бедно выпустили работу с асинхронностью аж в 2023 году по старой доброй традиции сделали это слегка неуклюже на мой взгляд. В том же Go достаточно ключевого слова go в Java это возня с разными видами потоков.
Я фронтендер, мне было интересно и познавательно. Спасибо.
Пы сы: дотнет в глаза никогда не видел))
Async2 – это тот же механизм, что реализован в Swift (где await порой может ничего не стоить), или другой?
Подозреваю что статья - GPT перевод https://github.com/dotnet/runtimelab/blob/feature/async2-experiment/docs/design/features/runtime-handled-tasks.md ? :-)
Не совсем, какие то части действительно составлял используя ллмки, но не полностью. Объяснение же механизма async2 и вправду основано на документе по ссылке, на который я также оставил ссылку в конце статьи
Можете обновить часть "На текущий момент async2 остаётся экспериментом, доступным в runtimelab в ветке feature/async2-experiment" -- оно уже переехало в апстрим (main dotnet/runtime) и продолжается разработка уже там (довольно активно).
О, вот это классная новость.
никуда он не переехал, так и лежит в runtimelab - github.com/dotnet/runtimelab/tree/feature/async2-experiment
он переехал. вы ожидали увидеть удаленную ветку? зачем?
Не вижу ни одного упоминания переезда)
Тут пишут, что поставлен на холд, для улучшения текущей модели асинхронности. https://github.com/dotnet/runtimelab/issues/2398
https://github.com/dotnet/runtime/pull/114675 и https://github.com/dotnet/runtime/pull/114861 - вот переезд + много пулл-реквестов после этого
Текущая реализация асинхронности в C# представляет собой корутины, т.е. функции, которые работают кооперативно и могут уступать поток другим корутинам для исполнения. Разница в уровнях абстракции:
Корутины оперируют функциями, которые переключаются на нативном потоке ОС
Зеленые потоки реализуют собственный механизм многопоточности, управляемый рантаймом. Для функций это выглядит прозрачно, будто бы они исполняются на обычном потоке
Это не совсем корректное утверждение. Как именно исполняются корутины(Task) в C# отпределяет реализация абcтрактного TaskScheduler. Она может быть любой, в том числе ваша собственная, хоть even-loop, хоть ручное переключение для детерминированного поведения в тестах. По умолчанию используется пулл потоков и epoll для Ι/Ο и в этом смысле никакого отличия от того же GO нет, где так же горутины исполняются на пулле потоков системы(M: kernel-level threads). Разница там только в алгоритме роста этого пулла - в .net пулл растет если задача стоит в очереди более 500мс, в GO пулл фиксирован по GOMAXPROCS и новый поток создается только если горутина уходит на syscall, при этом при привышении GOMAXPROCS лишние потоки паркуются и сохраняются в пуле sched.midle
async2: эксперимент с поддержкой асинхронности в рантайме