Как scala Future разрешить асинхронное выполнение? - PullRequest
1 голос
/ 02 мая 2020

Я новичок в параллельном исполнении и scala. У меня есть несколько вопросов об использовании Future в scala.

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

Так что в следующем примере я ожидаю, что как только метод donutStock будет вызываться как элемент управления на основной поток должен go переслать, а затем основной поток должен вызвать второй метод donutStock в другом потоке.

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

Я прочитал, что мы должны выполнить операцию asyn c в главном потоке сервера. В чем преимущество асин c работы в таких случаях

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}

def donutStock(donut: String): Future[Int] =  Future {
  (1 until 100).foreach { value ⇒
    println(s"checking donut stock $donut")
  }
  10
}

donutStock("My Donut").onComplete{
  case Success(value) ⇒ println("Call 1 Completed")
  case Failure(exception) ⇒ println("Call 1 Failed")
}

donutStock("Your Donut").onComplete{
  case Success(value) ⇒ println("Call 2 Completed")
  case Failure(exception) ⇒ println("Call 2 Failed")
}

Ответы [ 2 ]

1 голос
/ 03 мая 2020

Когда будущее создается, оно обычно начинается сразу, используя один поток. Если в текущем контексте выполнения нет доступных потоков, то он может не начать ваше будущее сразу, а скорее подождать, пока поток не будет освобожден.

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

Обычно, у контекста выполнения будет больше доступных потоков (например, в глобальном контексте выполнения scala число потоков по умолчанию равно числу доступных потоков).

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

Вы можете уменьшить его, введя небольшую задержку после печати значения, например, добавив Thread.sleep(10) после println(s"checking donut stock $donut").

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

Во избежание этого вы можете дождаться обоих фьючерсов, используя Await, например:

import scala.concurrent._
import scala.concurrent.duration._

val f1 = donutStock("My Donut").onComplete{
  case Success(value) ⇒ println("Call 1 Completed")
  case Failure(exception) ⇒ println("Call 1 Failed")
}

val f2 = donutStock("Your Donut").onComplete{
  case Success(value) ⇒ println("Call 2 Completed")
  case Failure(exception) ⇒ println("Call 2 Failed")
}

val result1 = Await.result(f1, 1 second)
val result2 = Await.result(f2, 1 second)

Если мы можем подождать на будущее, каков вариант использования для onComplete обратного вызова? Например, это может быть полезно, когда мы определяем функцию, возвращающую Future, и мы не хотим блокировать ее, используя Await, но мы все еще хотим выполнить какое-то действие, когда будущее завершено.

Например, Вы можете изменить donutStock, как показано ниже:

def donutStock(donut: String, idx: Int): Future[Int] = {
  val f = Future {
    (1 until 100).foreach { value ⇒
      println(s"checking donut stock $donut")
    }
    10
  }

  //we don't block future, but onComplete callback will be still executed when future ends
  f.onComplete{
    case Success(value) ⇒ println(s"Call $idx Completed")
    case Failure(exception) ⇒ println(s"Call $idx Failed")
  }

  f 
}
0 голосов
/ 02 мая 2020

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

Чтобы использовать Futures, Scala требует, чтобы мы предоставили неявный контекст выполнения, который контролирует пул потоков, в котором выполняются Futures. Мы можем создать наши собственные контексты выполнения или использовать стандартный по умолчанию, которого обычно достаточно. Контекст выполнения по умолчанию поддерживается Fork Join Thread Pool. Из кода очевидно, что в примере используется неявный.

def donutStock(donut: String): Future[Int] =  Future {
  (1 until 100).foreach { value ⇒
    println(s"checking donut stock $donut")
  }
  10
}

Приведенный выше код будет выполняться в своем собственном потоке, когда функция donutStock(<string>) с типом возвращаемого значения Future[Int].

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

donutStock("My Donut").onComplete{
  case Success(value) ⇒ println("Call 1 Completed")
  case Failure(exception) ⇒ println("Call 1 Failed")
}

donutStock("Your Donut").onComplete{
  case Success(value) ⇒ println("Call 2 Completed")
  case Failure(exception) ⇒ println("Call 2 Failed")
}

После успешного завершения Future donutStock () обратный вызов onComplete получает объект Success, содержащий результат as 10.

...