Детали реализации
Дизайн данной библиотеки вдохновлен дизайном Future из языка Rust.
Однако сейчас содержит некоторые значимые изменения.
Принцип работы типа Future
Весь механизм организации асинхронного вычисления на основе 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.yield', который возвращает поток планировщику одновременно с пробуждениемFuture.
Дизайн отбрасывания Future прокидывает отбрасывание ко всем дочерним Future независимо от способа их создания.
Это позволяет отбрасывать запущенную на планировщике Future через IFutureTask
(который может быть преобразован в Future).
А также отбрасывать любую Future в альтернативах (например внутри Future.first).
Ожидаемые инварианты Future
Futureполностью синхронна извне, то есть методыPollиDropвзаимно не потокобезопасны и не должны вызываться одновременноFutureможет опрашиваться (Poll) повторно, даже если она не пробудилась.Futureможет пробуждаться несколько раз до повторного опроса.Dropидемпотентен и может вызываться многократно (По аналогии с Dispose)Dropне должен бросать исключения и должен произвести освобождение ресурсов и отмену дочерних Future.Futureсчитается терминальной (т.е. выполненной) после:- Вызова
Pollс результатомReady x, - Вызова
Pollс результатомTransit fut - Вызова
Pollс броском исключения (логический эквивалентReady (Error exn)) - Вызова
Drop(в т.ч. с броском исключения, хотя бросать исключения вDropнельзя)
- Вызова
- После достижения терминального состояния,
Futureне должна больше использоваться (т.е. не должны вызыватьсяPollиDrop) Futureдолжна быть доведена до терминального состояния, либо посредством окончания опроса, либо отменой, чтобы предотвратить утечку ресурсов.- Контекст для
Futureдолжен быть одним и тем же от первого опроса, до терминального состояния.Futureдостаточно сохранить контекст при первом опросе и не перезаписывать его. (Можно сказать, что каждый контекст соответствует одному запуску.) - Контексту каждой запущенной
Futureследует быть уникальным. Мотивация этого поведения в том, чтобы не пробуждать лишниеFuture.
Любое поведение не следующее инвариантам -- UB
Примечания
IStreamпо сути является расширением моделиFuture, поэтому инвариантыFutureкак правило верны и дляIStream
В отличие от реализации в Rust, я не заставляю Future обновлять последний сохраненный ею IContext при каждом опросе. Преимущества на обоих платформах мне не очевидны, а это ощутимо усложняет реализацию. Поэтому это может измениться в будущем.
Каждая
Futureможет быть использована как-либо (забайнджена, запихнула в комбинатор, запущена) только ОДИН раз
Есть некоторая коллизия с тем, что Future может выкидывать исключения как предметные, так и свои. Например, неявным результатом IVar может быть любое исключение. И одно из них это FutureTerminatedException. В этой точке будет не так прозрачно, исключение произошло в читающей Future или в записывающей. Но 0. Исключениям можно, у них сложный переезд из места появление в абсолютно случайное
- Возможные решения слишком муторные и не стоят того из-за исключения, которое означает проблему в библиотечном, а не пользовательском коде