Детали реализации
Дизайн данной библиотеки вдохновлен дизайном Future
из языка Rust.
Однако сейчас содержит некоторые значимые изменения.
Принцип работы типа Future
Весь мeханизм организации асинхронного вычисления на основе Future
сводится к опросу
самой Future
путём вызова Poll: IContext -> Poll<'a>
до завершения её вычисления.
Future
может быть отброшена вызовом Future.Drop: unit -> unit
Результатом опроса является текущее состояние вычисления Future
:
Pending
- вычислениеFuture
находится в процессе и еще не завершено.Ready x
- значение вычислено и опрос больше не требуется.Transit fut
- говорит о подмене текущейFuture
другой такого же типа. Уменьшает глубину опроса и предназначен для оптимизации bind выражаемых конструкций.
Если Future
не совершает асинхронную работу и может сразу вернуть
результат(возможно с некоторой работой), то при первом опросе должен возвращаться Ready
.
В противном случае Future
должна запомнить переданный Context
и вызвать его метод Wake
в случае завершения асинхронной работы, чтобы планировщик пробудил её от ожидания этой работы.
При написании Future стоит учитывать, что пока в ней выполняются блокирующие операции -- блокируется поток планировщика, на котором она запушена. Для долгих блокирующих операций (когда это возможно, например, в циклах которые сами не возвращают управление) можно применить
Future.yieldWorkflow
, который возвращает поток планировщику одновременно с пробуждениемFuture
.
Дизайн отбрасывания Future
прокидывает отбрасывание ко всем дочерним Future
независимо от способа их создания.
Это позволяет отбрасывать запущенную на планировщие Future
через IFutureTask
(который может быть преобразован в Future
).
А также отбрасывать любую Future
в альтернативах (например внутри Future.first
).
Ожидаемые инварианты Future
-
Future
полностью синхронна извне: методыPoll
иDrop
взаимно не потоко-безопасны и не должны вызываться одновременно (в т.ч. нельзя вызывать у однойFuture
методPoll
илиDrop
несколько раз одновременно). -
Future
может опрашиваться (Poll
) повторно, даже если она не пробудилась. -
Future
может пробуждаться несколько раз до повторного опроса. -
Future
считается терминальной (т.е. выполневшейся) после:- Вызова
Poll
с результатомReady x
, - Вызова
Poll
с результатомTransit fut
- Вызова
Poll
с броском исключения (логический эквиваленReady (Error exn)
) - Вызова
Drop
(в т.ч. с броском исключения, хотя бросать исключения вDrop
нельзя)
- Вызова
-
Когда
Future
переходит в терминальное состояние, она должна остановить (или отвязать от себя) её запущенные фоновые процессы и освободить занятые резурсы. -
После достижения терминального состояния,
Future
не должна больше использоваться (т.е. не должны вызыватьсяPoll
иDrop
) -
Future
должна быть доведена до терминального состояния, либо посредством окончания опроса, либо отменой, чтобы предотвратить утечку ресурсов и не привести к вечному блокированию. -
Контекст для
Future
должен быть одним и тем же от первого опроса, до терминального состояния.Future
достаточно сохранить контекст при первом опросе и не перезаписывать его. (Можно сказать, что каждый контекст соответствует одному запуску.) -
Контекст каждой запущенной
Future
должен быть уникальным. Мотивация этого поведения в том чтобы предотвратить пробуждение новыхFuture
в планировщике при утечке контекста старыми. -
При достижении терминального состояния
Future
не следует пробуждаться, т.е. следует позаботиться о том, чтобыWake
её контекста не мог больше использоваться. Однако повторное пробуждение после достижения терминального состояния допустимо по вышеописанному правилу. -
Drop
не должен бросать исключения и должен отработать корректно, чтобы произвести освобождение ресурсов. (РеализациямFuture
НЕ нужно заботиться об этом, но если рансай)
Примечания
Для реализаций
Future
любое поведение не следующее инвариантам -- UB, и может как обрабатываться особым способом, так и игнорироваться.
IStream
по сути является расширением моделиFuture
, поэтому инвариантыFuture
как правило верны и дляIStream
В отличие от реализации в Rust, я не заставляю Future обновлять последний сохраненный ею IContext при каждом опросе. Преимущества на обоих платформах мне не очевидны, а это ощутимо усложняет реализацию. Поэтому это может измениться в будущем.
Методы
Future
не предназначены для ручного вызова, т.к. требуют с собой деликатного обращения. По этой причинеIFutureTask
не наследуетFuture
, а имеет отображение в неё
Каждая
Future
может быть использована как-либо (забайнджена, запихнула в комбинатор, запущена) только ОДИН раз
Есть некоторая коллизия с тем, что Future может выкидывать исключения исключения как предметные, так и свои. Например, неявным результатом IVar может быть любое исключение. И одно из них это FutureTerminatedException. В этой точке будет не так прозрачно, исключение произошло в читающей Future или в записывающей. Но 0. Исключениям можно, у них сложный переезд из места появление в абсолютно случайное
- Возможные решения слишком муторные и не стоят того из-за исключения, которое означает проблему в библиотечном, а не пользовательском коде