Детали реализации

Дизайн данной библиотеки вдохновлен дизайном Future из языка Rust. Однако сейчас содержит некоторые значимые изменения.

Принцип работы типа Future

Весь механизм организации асинхронного вычисления на основе Future сводится к опросу самой Future путём вызова Poll: IContext -> Poll<'a> до завершения её вычисления. Future может быть отброшена вызовом Future.Drop: unit -> unit

Результатом опроса является текущее состояние вычисления Future:

  1. Pending - вычисление Future находится в процессе и еще не завершено.
  2. Ready x - значение вычислено и опрос больше не требуется.
  3. 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. Исключениям можно, у них сложный переезд из места появление в абсолютно случайное

  1. Возможные решения слишком муторные и не стоят того из-за исключения, которое означает проблему в библиотечном, а не пользовательском коде