Владимир Ситников писал(а):
Иными словами, если "yield" иконка означает "вернуть управление в вызывающий код", то нужна дуальная "дать один квант времени на такую-то схему" (выполнить до ближайшей yield точки)
Полезно рассмотреть вариации основных техник кооперативной многозадачности или взаимного исполнения процессов в каком-либо виде (активном и пассивном), даже если при моделировании явно нет речи о какой-то там диспетчеризации процессов. Акцент, прежде всего, на способе выражения операции "ожидание" и "возврат управления" (оставляя за рамками непосредственно возможные предметки, где задача организации операций может иметь явный прикладной характер управления расписанием).
Первый вариант -- как у того же Зюбина, в рамках платформы для контроллеров: упрощённо, в контексте глобального управляющего цикла условный диспетчер перебирает все возможные логические процессы и даёт каждому шанс поработать. И каждый процесс сам решает, готов ли он продолжать трассу исполнения. В таком случае для ожидания возникает "цикл-событие", как выше в теме (мол процесс, фактически, "сам себе доктор"), и возможен произвольный характер условия для акта ожидания, возникает любая композиция условий.
Икона аля "yield" означает возврат управления диспетчеру (мол теперь пусть другие поработают).
Владимир Ситников писал(а):
В этом плане мне понравилось как в Kotlin сделали suspending functions
В техниках как в Kotlin условный полный перебор (потенциально огромного количества) логических процессов не осуществляется, диспетчеризация выполнятся в зависимости от возникновения конкретных событий, к последним есть непосредственная привязка (упрощённый взгляд, в целом возможны и фоновые процессы и т.д.).
Ключевая схема:
result = computation.await()
, где операция вида await (явно или косвенно) выполняется "по месту" или где-то позже (для асинхронных процессов), или же точки взаимодействия разносятся по всяким конструкциям, как в тех же паттернах аля "генератор", в общем случае по схеме: вызов suspend-функции потенциально приостанавливает поток (с возможным переключением контекста исполнения), и где-то в другом месте условно когда-нибудь передадут данные в качестве результата для ожидающей функции.
Таким образом, для схем точки ожидания возникают в общем случае в двух формах. С одной стороны, когда вызывается suspend-функция, вроде как, происходит ожидание завершения её работы, т.е. ожидание окончания действия. С другой стороны, с "маршрутной" точки зрения, возникает предикат для дальнейшего прохождения пути. В общем, по-видимому, по вкусу -- отмечать или нет возникающие потенциальные задержки действий и возможные точки переключения контекста. Может быть косвенно при потребности неким способом, как здесь флажки:
https://forum.drakon.su/viewtopic.php?f=78&t=6068&start=40#p100412В то же время могут быть и ожидания как предикаты:
Код:
suspend fun selectFizzBuzz(fizz: ReceiveChannel<String>, buzz: ReceiveChannel<String>) {
select<Unit> {
fizz.onReceive { value ->
println("fizz -> '$value'")
}
buzz.onReceive { value ->
println("buzz -> '$value'")
}
}
}
Если вдруг "переводить" на язык Дракон-схем (ибо "лямбды" вписывать в схемы пока, вроде как, толком нет однозначного решения), то для операций аля "onReceive" напрашиваются какие-то "вопросы"-условия. Причём существует потребность в альтернативном ожидании (как в примере функция "select"). Но сам предикат (для каждого варианта) есть единая операция ожидания -- нет "добывающих себе данных" циклов с маршрутами, "разруливание" события происходит извне (предикат есть нечто вроде "заголовка" в ветке силуэта, или как идентификатор взведённого таймера при использовании "классического синхронизатора").
По-видимому, аналогичную размытость в семантике ожидания (да и в "расписании" совершаемых действий -- размытое семантическое понимание когда и где выполняется работа и реальная передача данных) имеют техники "мониторов" или "критических секций" как в различных методиках "активных объектов" или "задач" в Ada:
https://en.wikibooks.org/wiki/Ada_Programming/TaskingОднозначно идентифицировать сеть работ (т.е. однозначно определить исполняемые функциональные задачи с передачей "предметов труда") вместе с предикатами маршрутов позволяют техники Esterel 80-х гг., где для императивной событийной формы (в целом формы алгоритмических систем в тех краях есть разные, и очень разные) вместо "лямбды на лямбде" используют разделяемые переменные, что предусматривает в т.ч. и "широковещательные" реакции на события как на "макроуровне" (не только локализованную двустороннюю связь, поставщик данных может не знать потребителей, как и потребители друг друга, все они могут возникать и в динамике).
В качестве примера можно взглянуть на упрощённый проект Céu -- полуакадемическая разработка по мотивам (не все оригинальные техники используются, в чём-то есть и свои перегибы):
http://ceu-lang.org/(для поверхностного взгляда достаточно видео-демки). Кратко: объекты-автоматы общаются через спецпеременные входа/выхода (input/output), включая и асинхронные процессы. После возникновения входа могут быть внутренние каскады событий (переменные event), после завершения которых может быть оформлен выход. В данном случае используются как раз "дуальные" операции ожидания await и "широковещательной эмиссии" данных emit (при "публикации" данных процесс приостанавливается до тех пор, пока не отработают все связанные по событию внутренние и внешние автоматы). Есть вспомогательные операторы поверх базовых операций, как "every" -- ожидающий цикл. Однако такие операторы как "watching" есть аля наблюдательные спецсекции, которые прерывают исполнение всех вложенных блоков, в т.ч. на произвольной глубине текущего контекста исполнения.
В общем, альтернатива для той же switch-технологии как на "макроуровне".
Итого, достаточно даже рассмотренных подходов, чтобы оценить неоднозначность в способах универсального отражения на схемах событий и переключения контекста.