Как отказаться от других Фьючерсов, если критическое Будущее закончено в Scala? - PullRequest
2 голосов
/ 04 мая 2020

Допустим, у меня есть три удаленных вызова для создания моей страницы. Один из них (X) имеет решающее значение для страницы, а два других (A, B) просто используются для улучшения восприятия.

Поскольку criticalFutureX слишком важно, чтобы на него влияли futureA и futureB, поэтому я хочу, чтобы общая задержка всех удаленных вызовов была не более X.

Это означает, что в случае окончания criticalFutureX я хочу отбросить futureA и futureB.

val criticalFutureX = ...
val futureA = ...
val futureB = ...

// the overall latency of this for-comprehension depends on the longest among X, A and B
for {
  x <- criticalFutureX
  a <- futureA
  b <- futureB
} ...

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

Latencies:
X: |----------|
A: |---------------|
B: |---|

O: |---------------| (overall latency)

Существует firstCompletedOf , но его нельзя использовать, чтобы явно сказать "в случае завершения критического будущего".

Есть ли что-то вроде следующего?

val criticalFutureX = ...
val futureA = ...
val futureB = ...

for {
  x <- criticalFutureX
  a <- futureA // discard when criticalFutureX finished
  b <- futureB // discard when criticalFutureX finished
} ...

X: |----------|
A: |-----------... discarded
B: |---|

O: |----------| (overall latency)

Ответы [ 2 ]

4 голосов
/ 04 мая 2020

Вы можете достичь этого с обещанием


  def completeOnMain[A, B](main: Future[A], secondary: Future[B]) = {
    val promise = Promise[Option[B]]()
    main.onComplete {
      case Failure(_) =>
      case Success(_) => promise.trySuccess(None)
    }
    secondary.onComplete {
      case Failure(exception) => promise.tryFailure(exception)
      case Success(value)     => promise.trySuccess(Option(value))
    }
    promise.future
  }

Некоторые тестовые коды

  private def runFor(first: Int, second: Int) = {

    def run(millis: Int) = Future {
      Thread.sleep(millis);
      millis
    }

    val start = System.currentTimeMillis()
    val combined = for {
      _ <- Future.unit
      f1 = run(first)
      f2 = completeOnMain(f1, run(second))
      r1 <- f1
      r2 <- f2
    } yield (r1, r2)

    val result = Await.result(combined, 10.seconds)
    println(s"It took: ${System.currentTimeMillis() - start}: $result")
  }

  runFor(3000, 4000)
  runFor(3000, 1000)

Производит

It took: 3131: (3000,None)
It took: 3001: (3000,Some(1000))
0 голосов
/ 12 мая 2020

Эту задачу очень трудно выполнить эффективно, надежно и безопасно с помощью Scala стандартной библиотеки Futures. Нет способа прервать Future, который еще не завершен, а это означает, что даже если вы решите игнорировать его результат, он все равно будет продолжать работать и тратить впустую память и процессорное время. И даже если был способ прервать выполнение Future, нет способа гарантировать, что выделенные ресурсы (сетевые подключения, открытые файлы и т. Д. c.) Будут правильно освобождены.

I хотел бы отметить, что в реализации, данной Иваном Станиславчу c, есть ошибка: если main Future не удастся, то обещание никогда не будет выполнено, что вряд ли будет тем, что вы хотите.

Поэтому я настоятельно рекомендую изучить современные системы одновременных эффектов, такие как ZIO или кошачий эффект. Это не только безопаснее и быстрее, но и намного проще. Вот реализация с ZIO, у которой нет этой ошибки:

import zio.{Exit, Task}
import Function.tupled

def completeOnMain[A, B](
  main: Task[A], secondary: Task[B]): Task[(A, Exit[Throwable, B])] =
  (main.forkManaged zip secondary.forkManaged).use {
    tupled(_.join zip _.interrupt)
  }

Exit - это тип, описывающий, как завершилась задача secondary, то есть путем успешного возврата B или из-за ошибка (типа Throwable) или из-за прерывания.

Обратите внимание, что этой функции можно дать гораздо более сложную подпись, которая расскажет вам намного больше о том, что происходит, но я хотел бы упростить ее здесь .

...