корутины это про то, где надо высоко конкурентный код писать
Коротины - это скорее про удобство разработки, про то чтобы писать асинхронный код в обычном легко читаемом синхронном стиле, без колбек хела.
Ну и статья в целом, о том что Project Loom не ограничивается виртуальными потоками: с помощью не публичного API можно реализовать свои корутины, отличающиеся от Котлиновских
это вызов диспатчера, который создает корутину (которая, кстати, начинает выполняться в этом же вызове метода dispatch, т. к. он вызван в UI потоке). Но, конечно же, я имею ввиду отсутствие колбеков внутри корутины.
конкретно с synchronized, да пока Loom работает плохо, но c другими примитивами синхронизации(ReentrantLock, Semaphore и т.д.) виртуальные потоки работать умеют.
Для конкретно моих Loom-корутин добавить аналог kotlinx.coroutines.sync.Mutex не сложно, но да нужно будет либо полностью переходить на свои примитивы синхронизации, либо держать в голове, можем ли мы воспользоваться в данном куске кода стандартными средствами синхронизации или нет. Это, пожалуй, основной аргумент в пользу раскраски кода
Но можно сделать раскраску кода обязательной и для Loom-корутин, воспользовавшись аннотациями и дополнительными линтерами, которые будут проверять раскраску во время сборки проекта.
Про synchronized конечно верно. Но его и в Kotlin-корутинах нельзя использовать. Это общая проблема блокировок в асинхронном коде, существующая как и в Kotlin- , так в Loom-корутинах
Но, справедливости ради, Kotlin-корутины не обязывают использовать только их для эффективного IO. Решение можно комбинировать: если `Dispatchers.IO` (диспатчер, в котором принято запускать блокирующий код в Kotlin) запускать на виртуальных потоках, то можно автоматически получить неблокирующие IO вызовы. Другое дело, что Loom-корутинам в принципе не нужно раскрашивать код и их проще отлаживать
Kotlin-корутины хорошо оптимизированы и на некоторых моих тестах выполняются быстрее Loom-корутин.
Kotlin-корутины - универсальное кроссплатформенное решение, не требующее поддержки со стороны рантайма. Что важно, учитывая популярность Kotlin на Android и таргетинг на разные платформы. Loom-корутины же требуют JDK, причем одной из самых новых версий.
Для генерации заглушек мне не нужно знать, в каком порядке они будут вызываться - они могут вызывать себя как угодно, в каком угодно порядке.
Это обеспечивается за счет использования MethodHandle API. Просто, грубо говоря, при вызове метода заглушки я ему передаю массив MethodHandle[] которые нужно вызвать.
Конкретный порядок я определяю именно в момент "пробуждения" корутины. В вашем примере все будет работать
upd: Если вопрос в том, как я определяю стек вызова во время "пробуждения" - в Kotlin stdlib есть функция для этого.
Классы-заглушки нужны для эмуляции стека вызова. Например, рассмотрим 3 функции, которые по очереди друг друга вызывают:
suspend fun fun1() {
fun2()
delay(10)
}
suspend fun fun2() {
fun3()
delay(10)
}
suspend fun fun3() {
delay(10)
throw Exception()
}
Мы вызваем fun1, после этого вызываются fun2 и fun3 и корутина "засыпает".
В обычном случае после "пробуждения" будет вызвана только функция fun3 и когда сгенерируется исключение, в его трейсе будет содержаться только этот метод.
C моей библиотекой происходит по-другому: генерируется класс-заглушка, содержащий методы fun1 и fun2, fun1 вызывает fun2, а fun2 "будит" корутину. В результате стек вызова содержит все 3 метода. И стектрейс исключения содержит их все.
Сколько их генериться?
Столько же, сколько содержится классов с suspend-функциями.
Когда?
Для класса A генерируется заглушка во время первого "пробуждения" корутины, содержащей в стеке методы класса A. После этого заглушка кешируется и повторные "пробуждения" не приводят к генерации заглушки.
Можно же, наверное, создать руками любой стектрейс, не?
Создать-то можно. Нельзя переписать стектрейс исключения, сгенерированного во время выполнения корутины.
про C# я вообще молчу, мне в силу ряда причин нужен язык на JVM.
И я скорее про пример из habr.com/ru/company/funcorp/blog/558412/#comment_23070268 — сами же сказали, что создадите дублирующую переменную (однобуквенную или нет — не столь важно).
ок. Если вам нравится придумывать названия переменных для каждой ветки, Java, C# — ваш выбор.
А мне нравится smart cast — отличная идея, которая сохраняет строгую типизацию в языке и позволяет не плодить лишних переменных.
Если често — ужас, не зная синтаксиса C#, сложно что-то понять.
Там логика «вызвали метод, если вернулся нулл то ретурн нулл сразу, иначе вот у нас есть переменная с каким-то значением». Вопрос — зачем мне мусорное значение которое вернула TryGetInitWithValue(type) в скоупе?
Если я правильно вас понял — вы хотите вызвать функцию и если ее значение нужного типа, что-то с ней сделать и не выносить переменную в основной скоуп. Ок, в Kotlin это делается так:
(calcValue() as? RequiredType)?.also { value ->
//в этом скопе (и только в нем) есть value типа RequiredType
}
Хороший пример просто, что вот никаких 90% с проверкой существующей переменной (когда у вас есть выбор) нет. Когда выбора нет — тогда другое дело, конечно, тогда я думаю процент даже не 90 а 100 :)
А как бы вы реализовали такую функцию без создания доп. переменной?
fun addValue(value: Any) {
if (value is ListenableValue) {
value.onAdd(this) // onAdd есть у типа ListenableValue
}
set.add(value)
}
ну хорошо, для вычисляемых значений Java ввела хороший синтаксис проверки типа с объявлением новой переменной.
Но на моей практике 90% случаев проверки типа происходит на существующей переменной, в этом случае в Java я обязан объявить новую переменную отличающуюся только типом, что выглядет убого (опять же на мой взгляд).
Пункт 1. легко решается просто указанием JvmName для функции. В вашем примере будет:
fun toString(a: Any?): String? {
return a?.toString()
}
@JvmName("toStringNotNull")
fun toString(a: Any): String {
return a.toString()
}
про п. 2 согласен, lateinit — костыль, который вставили для поддержки существующих Java библиотек. Но даже в вашем примере, я бы не стал писать lateinit, а просто инициализировал Id нулем, т.к. нет смысла помечать lateinit примитивные поля.
Не нужно никакой диверсии. В мире Java-бекенда вы будете использовать JDBC — это Java API для БД. Далее вы, как разработчик, сделали select, проверили, что искомый столбец not null и отметили поле в вашем DOA-объекте not-nullable.
А через месяц другой разработчик сделал alter на таблице, убрал not-null и стал писать строки с null.
Ровно такая же система null-safety и в Kotlin. Люди, которые её ругают, делают это по 2ум причинам:
В Java нет такого null-safety. И никто не мешает в Java записать в non-null поле этот самый null. Учитывая, что большинство используемых библиотек написано на Java, этот аргумент имеет место быть.
Есть способы обойти null-safety через двойной восклицательный знак и lateinit.
Всё так. Единственное, с чем не соглашусь - это
Коротины - это скорее про удобство разработки, про то чтобы писать асинхронный код в обычном легко читаемом синхронном стиле, без колбек хела.
Ну и статья в целом, о том что Project Loom не ограничивается виртуальными потоками: с помощью не публичного API можно реализовать свои корутины, отличающиеся от Котлиновских
это вызов диспатчера, который создает корутину (которая, кстати, начинает выполняться в этом же вызове метода dispatch, т. к. он вызван в UI потоке).
Но, конечно же, я имею ввиду отсутствие колбеков внутри корутины.
конкретно с
synchronized
, да пока Loom работает плохо, но c другими примитивами синхронизации(ReentrantLock
,Semaphore
и т.д.) виртуальные потоки работать умеют.Для конкретно моих Loom-корутин добавить аналог
kotlinx.coroutines.sync.Mutex
не сложно, но да нужно будет либо полностью переходить на свои примитивы синхронизации, либо держать в голове, можем ли мы воспользоваться в данном куске кода стандартными средствами синхронизации или нет.Это, пожалуй, основной аргумент в пользу раскраски кода
Но можно сделать раскраску кода обязательной и для Loom-корутин, воспользовавшись аннотациями и дополнительными линтерами, которые будут проверять раскраску во время сборки проекта.
Про
synchronized
конечно верно.Но его и в Kotlin-корутинах нельзя использовать. Это общая проблема блокировок в асинхронном коде, существующая как и в Kotlin- , так в Loom-корутинах
Но, справедливости ради, Kotlin-корутины не обязывают использовать только их для эффективного IO.
Решение можно комбинировать: если `Dispatchers.IO` (диспатчер, в котором принято запускать блокирующий код в Kotlin) запускать на виртуальных потоках, то можно автоматически получить неблокирующие IO вызовы.
Другое дело, что Loom-корутинам в принципе не нужно раскрашивать код и их проще отлаживать
Вижу 2 основных минуса перед Kotlin-корутинами:
Kotlin-корутины хорошо оптимизированы и на некоторых моих тестах выполняются быстрее Loom-корутин.
Kotlin-корутины - универсальное кроссплатформенное решение, не требующее поддержки со стороны рантайма. Что важно, учитывая популярность Kotlin на Android и таргетинг на разные платформы. Loom-корутины же требуют JDK, причем одной из самых новых версий.
Вариант с плагином не рассматривал. Но предположу, что с ним будут две проблемы:
API для плагинов компилятора пока не стабилизорован
и как быть с внешними зависимостями, которые уже скомпилированы без плагина.
Для генерации заглушек мне не нужно знать, в каком порядке они будут вызываться - они могут вызывать себя как угодно, в каком угодно порядке.
Это обеспечивается за счет использования MethodHandle API. Просто, грубо говоря, при вызове метода заглушки я ему передаю массив MethodHandle[] которые нужно вызвать.
Конкретный порядок я определяю именно в момент "пробуждения" корутины. В вашем примере все будет работать
upd:
Если вопрос в том, как я определяю стек вызова во время "пробуждения" - в Kotlin stdlib есть функция для этого.
? Баги/вопросы/предложения/замечания можете оставлять в Github issue, Gitter или в личном сообщении мне)
Классы-заглушки нужны для эмуляции стека вызова. Например, рассмотрим 3 функции, которые по очереди друг друга вызывают:
Мы вызваем
fun1
, после этого вызываютсяfun2
иfun3
и корутина "засыпает".В обычном случае после "пробуждения" будет вызвана только функция
fun3
и когда сгенерируется исключение, в его трейсе будет содержаться только этот метод.C моей библиотекой происходит по-другому: генерируется класс-заглушка, содержащий методы
fun1
иfun2
,fun1
вызываетfun2
, аfun2
"будит" корутину. В результате стек вызова содержит все 3 метода. И стектрейс исключения содержит их все.Столько же, сколько содержится классов с suspend-функциями.
Для класса A генерируется заглушка во время первого "пробуждения" корутины, содержащей в стеке методы класса A. После этого заглушка кешируется и повторные "пробуждения" не приводят к генерации заглушки.
Создать-то можно. Нельзя переписать стектрейс исключения, сгенерированного во время выполнения корутины.
И я скорее про пример из habr.com/ru/company/funcorp/blog/558412/#comment_23070268 — сами же сказали, что создадите дублирующую переменную (однобуквенную или нет — не столь важно).
А мне нравится smart cast — отличная идея, которая сохраняет строгую типизацию в языке и позволяет не плодить лишних переменных.
Если я правильно вас понял — вы хотите вызвать функцию и если ее значение нужного типа, что-то с ней сделать и не выносить переменную в основной скоуп. Ок, в Kotlin это делается так:
А как бы вы реализовали такую функцию без создания доп. переменной?
Но на моей практике 90% случаев проверки типа происходит на существующей переменной, в этом случае в Java я обязан объявить новую переменную отличающуюся только типом, что выглядет убого (опять же на мой взгляд).
про п. 2 согласен, lateinit — костыль, который вставили для поддержки существующих Java библиотек. Но даже в вашем примере, я бы не стал писать lateinit, а просто инициализировал Id нулем, т.к. нет смысла помечать lateinit примитивные поля.
не нравится let, есть простое как дверь решение: разнести объявление переменной и проверку типа
А через месяц другой разработчик сделал alter на таблице, убрал not-null и стал писать строки с null.
2. В Java 14, как уже отмечали, прямо как и в С#
Насчет того, что для вычисляемых значений синтаксис чуть красивее получается в Java 14 — не спорю.