Future는 아직 계산되지 않은 값을 나타내며, 이 값은 언젠가 나중에 계산될 수 있다. 스칼라에서는 scala.concurrent.Future 패키지에 Future가 정의되어 있으며, 아카에서도 이를 사용한다.
퓨처는 다음과 같은 상황에서 유용하다.
긴 실행 시간이 소요되는 작업을 수행할 때.
네트워크 호출 또는 파일 읽기와 같은 느린 작업을 수행할 때.
여러 작업을 병렬로 실행하고 그 결과를 기다려야 할 때.
아카에서는 주로 두 가지 방법으로 Future를 사용한다.
액터에게 메시지를 보내고, 그 결과를 Future 로 받는다. 이를 위해서는 ask 메서드나 ? 연산자를 사용한다.
val future = ask(helloActor, SayHello("John"))
val future = helloActor ? SayHello("John")
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
implicit val timeout: Timeout = Timeout(5.seconds)
val future = helloActor ? SayHello("John")
이미 계산된 Future 값을 다른 액터에게 전달할 수 있다. pipeTo메서드를 사용하여 Future 의 결과를 액터에게 전달할 수 있다.
import akka.pattern.pipe
val futureResult: Future[String] = ...
futureResult pipeTo anotherActor
아래는 스칼라로 아카의 퓨처를 사용하는 예시 코드이다.
import akka.actor.{ Actor, ActorSystem, Props }
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
// 액터 메시지 정의
case class SayHello(name: String)
case class HelloResponse(message: String)
// HelloActor 정의
class HelloActor extends Actor {
def receive = {
case SayHello(name: String) =>
sender ! HelloResponse(s"Hello, $name!")
}
}
object AskExample {
def main(args: Array[String]): Unit = {
// 액터 시스템 생성
val system = ActorSystem("AskExampleSystem")
// HelloActor의 인스턴스 생성
val helloActor = system.actorOf(Props[HelloActor], "helloActor")
// Timeout 정의 (액터에게 응답을 기다리는 시간)
implicit val timeout: Timeout = Timeout(5.seconds)
// ask 메서드를 사용하여 Future 얻기
val future = helloActor ? SayHello("John")
// Future 결과 처리
future.map {
case HelloResponse(message) => println(s"Received: $message")
case _ => println("Something went wrong")
}
// 액터 시스템 종료 예시 (실제 애플리케이션에서는 적절한 시점에 종료해야 함)
// system.terminate()
}
}
메시지를 보내고자 하는 목적지에 해당하는 액터는 ask 메서드에 인수로 전달되며,
보내려는 메시지와 타임아웃 객체도 인수로 전달된다.
위의 예제에서 HelloActor 는 SayHello 메시지를 받으면 HelloResponse 메시지를 sender 에게 다시 보낸다.
ask 메서드 (? 연산자 ) 를 사용하여 HelloActor 에게 SayHello 메시지를 보내고, 결과를 Future 로 받는다.
Future 의 결과는 map 함수를 사용하여 처리할 수 있다.
위의 예시에서는 결과가 HelloResponse 타입이면 그 내용을 출력하고, 그렇지 않으면 오류 메시지를 출력하고 있다.
Future가 성공하거나 실패할 때 수행할 동작을 지정할 수 있는 여러가지 방법이 있다.
onComplete이 메서드는 Future 가 완성되었을 때 호출되는 콜백을 설정한다.
Success 또는 Failure 객체를 인자로 받아 처리한다.
import scala.concurrent.Future
import scala.util.{Success, Failure}
import scala.concurrent.ExecutionContext.Implicits.global
val future: Future[Int] = Future {
42 // 대신 실제로 어떤 비동기 작업이 이루어질 수 있습니다.
}
future.onComplete {
case Success(value) => println(s"Success! The answer is $value.")
case Failure(e) => println(s"An error has occurred: ${e.getMessage}")
}
// result
성공적으로 완료됐다면: "Success! The answer is 42."
실패했다면: "An error has occurred: [에러 메시지]"
onSuccess 는 Future 가 성공했을 때 실행되고, 성공한 값만 인자로 받는다.
onFailure 는 Future 가 실패했을 때 실행되고, 예외만을 인자로 받는다.
future.onSuccess {
case value => println(s"Success! The answer is $value.")
}
future.onFailure {
case e => println(s"An error has occurred: ${e.getMessage}")
}
// result
성공적으로 완료됐다면 (onSuccess): "Success! The answer is 42."
실패했다면 (onFailure): "An error has occurred: [에러 메시지]"
map 과 flatMap 은 Future 의 결과에 함수를 적용한다.
이 함수들은 Future 가 성공적으로 완료되면 호출된다.
val futurePlusOne: Future[Int] = future.map { value =>
value + 1
}
val futurePlusTwo: Future[Int] = future.flatMap { value =>
Future.successful(value + 2)
}
// result
futurePlusOne의 값은 43
futurePlusTwo의 값은 44
이 메서드들은 Future 가 실패했을 때 대체 값을 제공하거나, 대체 Future 를 제공한다.
val recovered: Future[Int] = future.recover {
case e: Exception => -1
}
val recoveredWith: Future[Int] = future.recoverWith {
case e: Exception => Future.successful(-1)
}
// result
원래 Future가 성공했다면, recovered와 recoveredWith는 원래 Future의 값, 즉 42를 가지게 됨.
원래 Future가 실패했다면, recovered와 recoveredWith는 -1 값을 가지게 됨.