Interact with Akka actor outside actors
Asked Answered
H

3

13

I want to interact with Akka actors from my own thread. Currently, i do like so:

val res = Await.result(aref ? GroupReceive(fromRank), timeout.duration).asInstanceOf[T]

But I am unsure how this actually interacts with my thread? I wish for the receive to be asynchronous, i.e. I want to hang up the thread while receiving to allow for some other work to be done. I just recently read about Akka inbox system. inbox akka api

I think I recall that Await creates a new actor every time. What are the differences between await+ask and inbox, and can someone give me an example of how to create an inbox and use it to communicate with actors from "the outside" ?

EDIT Just to clarify, I don't want the same thread to continue working, I want it to stop hogging a cpu-core and leave other threads to work until it receives something, then wake up again.

Hydropic answered 24/5, 2013 at 9:2 Comment(11)
In akka you should better use mapTo[Type] instead of asInstanceOfSaad
What is the benefit? :)Hydropic
asInstanceOf throws an exception, when it fails. mapTo returns a failed future.Capriccio
The dangerous thing is, that asInstanceOf might actually succeed and fail later, when a specific method is called.Capriccio
Also Await can't create new actors, it's sole purpose is to block execution until awaiting for the result. The only way to create an actor is through system.actorOf(...)Saad
Does it hog the thread though?Hydropic
Yes it is, you can read about use of akka and futures hereSaad
There is an example in the documentation.Gingivitis
@AlexIv: system.actorOf(...) is not the only way to create an actor. ask or ? creates a new actor, ActorDSL.inbox creates a new actor.Gingivitis
@Gingivitis sorry never used akka's dsl, but as i can understand from this line it doesn't create a new actor, but returns a future with an actor referenceSaad
@Gingivitis it returns a new ActorRef with ActorSelection mechanism, but not a new ActorSaad
S
10

As written in the Akka's Future documentation, using Await blocks current thread until awaiting for the result.

Example

import scala.concurrent.Await
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._

implicit val timeout = Timeout(5 seconds)
val future = actor ? msg // enabled by the “ask” import
val result = Await.result(future, timeout.duration).asInstanceOf[String]

This will cause the current thread to block and wait for the Actor to 'complete' the Future with it's reply.

Use With Actors

Saad answered 24/5, 2013 at 9:17 Comment(6)
Can you elaborate on the difference between this and the use of inbox?Hydropic
@Hydropic Sorry never used akka's dsl, but as i can see the main usage of inbox is that ask can't 1) receiving multiple replies and 2) watching other actors’ lifecycle. The InboxSaad
@Gingivitis still disagree with you, ask return a Future with ActorRef through an ActorSelection, but it doesn't creates a new ActorSaad
@Alexlv, if you use ask outside of an Actor, a temporary actor is created under the hood. This is because in order to respond you need an actor ref to send back to via the ! operator. The actor is short lived and is stopped after the response is received or a timeout occurs.Trinidadtrinitarian
how could I use this with a BroadcastRouter that should reply several times for each future? Thank you.Brilliantine
No need to be blocking. As mentioned in the docs you can import scala.concurrent.Future and akka.pattern.ask, then val future: Future[String] = ask(actor, msg).mapTo[String]Scaffold
E
6

The Await.receive is part of the Scala concurrency API and has nothing to do with actors. Its purpose is the block the current thread until the provided future completes, or the timeout limit kicks in and the whole thing ends in a timeout exception.

The ask operator ? will indeed create a temporary actor with the sole purpose of awaiting for the reply from the actor pointed to by the aref variable and completing the future you got when you called the ask operator with the received reply.

So your code is essentially blocking the entire thread. As it was indicated, if you want to free up the current thread and continue doing some other work you can attach a callback to the future.

implicit val ctx: ExecutionContext = //provide execution context here
implicit val timeout: Timeout = // provide timeout here
aref ? GroupReceive(fromRank)) onSuccess { res =>
   //do something with res here, asynchronously
}
// some other code which runs without being blocked...

The above code can be rewritten with the actor DSL you mentioned above:

import akka.actor.ActorDSL._
implicit val actorSystem: ActorSystem = // provide an actor system here or any actor ref factory

actor(new Act {
  aref ! GroupReceive(fromRank)
  context.setReceiveTimeout(timeout) //optional
  become {
    case ReceiveTimeout => {
      //handle the timeout
      context.stop(self)
    }
    case res => {
      //do your thing with res, asynchronously
      context.stop(self)
    }
  }
}

//some other code which won't wait for the above operations

The latter version also creates a new temporary actor which sends the GroupReceive message and then waits for a reply, after which it kills itself.

The bottom line is that in order to receive a message from an actor you must be an actor yourself. Actors can't just send message to something other than an ActorRef.

So either you use the ask pattern which creates a temporary actor behind the scenes and manages this temporary actor's lifecycle itself, exposing to you just a nice simple future to work with, or you can create the temporary actor yourself, but then you have to manage its lifecycle (i.e. remember to kill it once it has done its job)

Choose the option that suits you best.

Enrique answered 24/5, 2013 at 11:9 Comment(1)
Regarding the DSL example part of your response: although this works for higher latency requests, for very quick request/response it may fail because become takes some time to kick in. A DSL action should be initialised exactly like in this question to always work: #17852349.Stab
T
0

If you don't want to block on the calling side then don't use Await, use the non blocking callbacks instead like onSuccess, onFailure and onComplete. When you do this, a future task is put into whatever ExecutionContext is in scope at time of ask (?). When a response is received, this callback is invoked asynchronously via the ExecutionContext. This way you avoid blocking all together in the thread that is making the request to the actor and then the callback is handled in the thread pool tied to the ExecutionContext.

In addition, I believe the inbox stuff you mention is geared towards testing out actor stuff in the REPL (at least that's what the docs on ActorDsl) states. Stick with the approach you have of using ask from outside the actor. Let akka create the short lived actor that it needs for the communication under the hood for non-actor to actor calls. Then just switch to a non blocking callback as I suggested above. I believe that's what you are looking for.

Trinidadtrinitarian answered 24/5, 2013 at 10:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.